//===- SemaSYCL.cpp - Semantic Analysis for SYCL constructs ---------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
// This implements Semantic Analysis for SYCL constructs.
//===----------------------------------------------------------------------===//

#include "clang/Sema/SemaSYCL.h"
#include "TreeTransform.h"
#include "clang/AST/Mangle.h"
#include "clang/AST/SYCLKernelInfo.h"
#include "clang/AST/StmtSYCL.h"
#include "clang/AST/SubobjectVisitor.h"
#include "clang/AST/TypeOrdering.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Sema/Attr.h"
#include "clang/Sema/ParsedAttr.h"
#include "clang/Sema/Sema.h"

using namespace clang;

// -----------------------------------------------------------------------------
// SYCL device specific diagnostics implementation
// -----------------------------------------------------------------------------

SemaSYCL::SemaSYCL(Sema &S) : SemaBase(S) {}

Sema::SemaDiagnosticBuilder SemaSYCL::DiagIfDeviceCode(SourceLocation Loc,
                                                       unsigned DiagID) {
  assert(getLangOpts().SYCLIsDevice &&
         "Device diagnostics Should only be issued during device compilation");
  SemaDiagnosticBuilder::Kind DiagKind = SemaDiagnosticBuilder::K_Nop;
  FunctionDecl *FD = SemaRef.getCurFunctionDecl(/*AllowLambda=*/true);
  if (FD) {
    Sema::FunctionEmissionStatus FES = SemaRef.getEmissionStatus(FD);
    switch (FES) {
    case Sema::FunctionEmissionStatus::Emitted:
      DiagKind = SemaDiagnosticBuilder::K_ImmediateWithCallStack;
      break;
    case Sema::FunctionEmissionStatus::Unknown:
    case Sema::FunctionEmissionStatus::TemplateDiscarded:
      DiagKind = SemaDiagnosticBuilder::K_Deferred;
      break;
    case Sema::FunctionEmissionStatus::OMPDiscarded:
      llvm_unreachable("OMPDiscarded unexpected in SYCL device compilation");
    case Sema::FunctionEmissionStatus::CUDADiscarded:
      llvm_unreachable("CUDADiscarded unexpected in SYCL device compilation");
    }
  }
  return SemaDiagnosticBuilder(DiagKind, Loc, DiagID, FD, SemaRef);
}

static bool isZeroSizedArray(SemaSYCL &S, QualType Ty) {
  if (const auto *CAT = S.getASTContext().getAsConstantArrayType(Ty))
    return CAT->isZeroSize();
  return false;
}

void SemaSYCL::deepTypeCheckForDevice(SourceLocation UsedAt,
                                      llvm::DenseSet<QualType> Visited,
                                      ValueDecl *DeclToCheck) {
  assert(getLangOpts().SYCLIsDevice &&
         "Should only be called during SYCL compilation");
  // Emit notes only for the first discovered declaration of unsupported type
  // to avoid mess of notes. This flag is to track that error already happened.
  bool NeedToEmitNotes = true;

  auto Check = [&](QualType TypeToCheck, const ValueDecl *D) {
    bool ErrorFound = false;
    if (isZeroSizedArray(*this, TypeToCheck)) {
      DiagIfDeviceCode(UsedAt, diag::err_typecheck_zero_array_size) << 1;
      ErrorFound = true;
    }
    // Checks for other types can also be done here.
    if (ErrorFound) {
      if (NeedToEmitNotes) {
        if (auto *FD = dyn_cast<FieldDecl>(D))
          DiagIfDeviceCode(FD->getLocation(),
                           diag::note_illegal_field_declared_here)
              << FD->getType()->isPointerType() << FD->getType();
        else
          DiagIfDeviceCode(D->getLocation(), diag::note_declared_at);
      }
    }

    return ErrorFound;
  };

  // In case we have a Record used do the DFS for a bad field.
  SmallVector<const ValueDecl *, 4> StackForRecursion;
  StackForRecursion.push_back(DeclToCheck);

  // While doing DFS save how we get there to emit a nice set of notes.
  SmallVector<const FieldDecl *, 4> History;
  History.push_back(nullptr);

  do {
    const ValueDecl *Next = StackForRecursion.pop_back_val();
    if (!Next) {
      assert(!History.empty());
      // Found a marker, we have gone up a level.
      History.pop_back();
      continue;
    }
    QualType NextTy = Next->getType();

    if (!Visited.insert(NextTy).second)
      continue;

    auto EmitHistory = [&]() {
      // The first element is always nullptr.
      for (uint64_t Index = 1; Index < History.size(); ++Index) {
        DiagIfDeviceCode(History[Index]->getLocation(),
                         diag::note_within_field_of_type)
            << History[Index]->getType();
      }
    };

    if (Check(NextTy, Next)) {
      if (NeedToEmitNotes)
        EmitHistory();
      NeedToEmitNotes = false;
    }

    // In case pointer/array/reference type is met get pointee type, then
    // proceed with that type.
    while (NextTy->isAnyPointerType() || NextTy->isArrayType() ||
           NextTy->isReferenceType()) {
      if (NextTy->isArrayType())
        NextTy = QualType{NextTy->getArrayElementTypeNoTypeQual(), 0};
      else
        NextTy = NextTy->getPointeeType();
      if (Check(NextTy, Next)) {
        if (NeedToEmitNotes)
          EmitHistory();
        NeedToEmitNotes = false;
      }
    }

    if (const auto *RecDecl = NextTy->getAsRecordDecl()) {
      if (auto *NextFD = dyn_cast<FieldDecl>(Next))
        History.push_back(NextFD);
      // When nullptr is discovered, this means we've gone back up a level, so
      // the history should be cleaned.
      StackForRecursion.push_back(nullptr);
      llvm::append_range(StackForRecursion, RecDecl->fields());
    }
  } while (!StackForRecursion.empty());
}

ExprResult SemaSYCL::BuildUniqueStableNameExpr(SourceLocation OpLoc,
                                               SourceLocation LParen,
                                               SourceLocation RParen,
                                               TypeSourceInfo *TSI) {
  return SYCLUniqueStableNameExpr::Create(getASTContext(), OpLoc, LParen,
                                          RParen, TSI);
}

ExprResult SemaSYCL::ActOnUniqueStableNameExpr(SourceLocation OpLoc,
                                               SourceLocation LParen,
                                               SourceLocation RParen,
                                               ParsedType ParsedTy) {
  TypeSourceInfo *TSI = nullptr;
  QualType Ty = SemaRef.GetTypeFromParser(ParsedTy, &TSI);

  if (Ty.isNull())
    return ExprError();
  if (!TSI)
    TSI = getASTContext().getTrivialTypeSourceInfo(Ty, LParen);

  return BuildUniqueStableNameExpr(OpLoc, LParen, RParen, TSI);
}

void SemaSYCL::handleKernelAttr(Decl *D, const ParsedAttr &AL) {
  // The 'sycl_kernel' attribute applies only to function templates.
  const auto *FD = cast<FunctionDecl>(D);
  const FunctionTemplateDecl *FT = FD->getDescribedFunctionTemplate();
  assert(FT && "Function template is expected");

  // Function template must have at least two template parameters.
  const TemplateParameterList *TL = FT->getTemplateParameters();
  if (TL->size() < 2) {
    Diag(FT->getLocation(), diag::warn_sycl_kernel_num_of_template_params);
    return;
  }

  // Template parameters must be typenames.
  for (unsigned I = 0; I < 2; ++I) {
    const NamedDecl *TParam = TL->getParam(I);
    if (isa<NonTypeTemplateParmDecl>(TParam)) {
      Diag(FT->getLocation(),
           diag::warn_sycl_kernel_invalid_template_param_type);
      return;
    }
  }

  // Function must have at least one argument.
  if (getFunctionOrMethodNumParams(D) != 1) {
    Diag(FT->getLocation(), diag::warn_sycl_kernel_num_of_function_params);
    return;
  }

  // Function must return void.
  QualType RetTy = getFunctionOrMethodResultType(D);
  if (!RetTy->isVoidType()) {
    Diag(FT->getLocation(), diag::warn_sycl_kernel_return_type);
    return;
  }

  handleSimpleAttribute<SYCLKernelAttr>(*this, D, AL);
}

void SemaSYCL::handleKernelEntryPointAttr(Decl *D, const ParsedAttr &AL) {
  ParsedType PT = AL.getTypeArg();
  TypeSourceInfo *TSI = nullptr;
  (void)SemaRef.GetTypeFromParser(PT, &TSI);
  assert(TSI && "no type source info for attribute argument");
  D->addAttr(::new (SemaRef.Context)
                 SYCLKernelEntryPointAttr(SemaRef.Context, AL, TSI));
}

void SemaSYCL::CheckDeviceUseOfDecl(NamedDecl *ND, SourceLocation Loc) {
  assert(getLangOpts().SYCLIsDevice &&
         "Should only be called during SYCL device compilation");

  // Function declarations with the sycl_kernel_entry_point attribute cannot
  // be ODR-used in a potentially evaluated context.
  if (FunctionDecl *FD = dyn_cast<FunctionDecl>(ND)) {
    if (const auto *SKEPAttr = FD->getAttr<SYCLKernelEntryPointAttr>()) {
      if (SemaRef.currentEvaluationContext().isPotentiallyEvaluated()) {
        DiagIfDeviceCode(Loc, diag::err_sycl_entry_point_device_use)
            << FD << SKEPAttr;
        DiagIfDeviceCode(SKEPAttr->getLocation(), diag::note_attribute) << FD;
      }
    }
  }
}

// Given a potentially qualified type, SourceLocationForUserDeclaredType()
// returns the source location of the canonical declaration of the unqualified
// desugared user declared type, if any. For non-user declared types, an
// invalid source location is returned. The intended usage of this function
// is to identify an appropriate source location, if any, for a
// "entity declared here" diagnostic note.
static SourceLocation SourceLocationForUserDeclaredType(QualType QT) {
  SourceLocation Loc;
  const Type *T = QT->getUnqualifiedDesugaredType();
  if (const TagType *TT = dyn_cast<TagType>(T))
    Loc = TT->getDecl()->getLocation();
  else if (const auto *ObjCIT = dyn_cast<ObjCInterfaceType>(T))
    Loc = ObjCIT->getDecl()->getLocation();
  return Loc;
}

static bool CheckSYCLKernelName(Sema &S, SourceLocation Loc,
                                QualType KernelName) {
  assert(!KernelName->isDependentType());

  if (!KernelName->isStructureOrClassType()) {
    // SYCL 2020 section 5.2, "Naming of kernels", only requires that the
    // kernel name be a C++ typename. However, the definition of "kernel name"
    // in the glossary states that a kernel name is a class type. Neither
    // section explicitly states whether the kernel name type can be
    // cv-qualified. For now, kernel name types are required to be class types
    // and that they may be cv-qualified. The following issue requests
    // clarification from the SYCL WG.
    //   https://github.com/KhronosGroup/SYCL-Docs/issues/568
    S.Diag(Loc, diag::warn_sycl_kernel_name_not_a_class_type) << KernelName;
    SourceLocation DeclTypeLoc = SourceLocationForUserDeclaredType(KernelName);
    if (DeclTypeLoc.isValid())
      S.Diag(DeclTypeLoc, diag::note_entity_declared_at) << KernelName;
    return true;
  }

  return false;
}

void SemaSYCL::CheckSYCLExternalFunctionDecl(FunctionDecl *FD) {
  const auto *SEAttr = FD->getAttr<SYCLExternalAttr>();
  assert(SEAttr && "Missing sycl_external attribute");
  if (!FD->isInvalidDecl() && !FD->isTemplated()) {
    if (!FD->isExternallyVisible())
      if (!FD->isFunctionTemplateSpecialization() ||
          FD->getTemplateSpecializationInfo()->isExplicitSpecialization())
        Diag(SEAttr->getLocation(), diag::err_sycl_external_invalid_linkage)
            << SEAttr;
  }
  if (FD->isDeletedAsWritten()) {
    Diag(SEAttr->getLocation(),
         diag::err_sycl_external_invalid_deleted_function)
        << SEAttr;
  }
}

void SemaSYCL::CheckSYCLEntryPointFunctionDecl(FunctionDecl *FD) {
  // Ensure that all attributes present on the declaration are consistent
  // and warn about any redundant ones.
  SYCLKernelEntryPointAttr *SKEPAttr = nullptr;
  for (auto *SAI : FD->specific_attrs<SYCLKernelEntryPointAttr>()) {
    if (!SKEPAttr) {
      SKEPAttr = SAI;
      continue;
    }
    if (!getASTContext().hasSameType(SAI->getKernelName(),
                                     SKEPAttr->getKernelName())) {
      Diag(SAI->getLocation(), diag::err_sycl_entry_point_invalid_redeclaration)
          << SKEPAttr << SAI->getKernelName() << SKEPAttr->getKernelName();
      Diag(SKEPAttr->getLocation(), diag::note_previous_attribute);
      SAI->setInvalidAttr();
    } else {
      Diag(SAI->getLocation(),
           diag::warn_sycl_entry_point_redundant_declaration)
          << SAI;
      Diag(SKEPAttr->getLocation(), diag::note_previous_attribute);
    }
  }
  assert(SKEPAttr && "Missing sycl_kernel_entry_point attribute");

  // Ensure the kernel name type is valid.
  if (!SKEPAttr->getKernelName()->isDependentType() &&
      CheckSYCLKernelName(SemaRef, SKEPAttr->getLocation(),
                          SKEPAttr->getKernelName()))
    SKEPAttr->setInvalidAttr();

  // Ensure that an attribute present on the previous declaration
  // matches the one on this declaration.
  FunctionDecl *PrevFD = FD->getPreviousDecl();
  if (PrevFD && !PrevFD->isInvalidDecl()) {
    const auto *PrevSKEPAttr = PrevFD->getAttr<SYCLKernelEntryPointAttr>();
    if (PrevSKEPAttr && !PrevSKEPAttr->isInvalidAttr()) {
      if (!getASTContext().hasSameType(SKEPAttr->getKernelName(),
                                       PrevSKEPAttr->getKernelName())) {
        Diag(SKEPAttr->getLocation(),
             diag::err_sycl_entry_point_invalid_redeclaration)
            << SKEPAttr << SKEPAttr->getKernelName()
            << PrevSKEPAttr->getKernelName();
        Diag(PrevSKEPAttr->getLocation(), diag::note_previous_decl) << PrevFD;
        SKEPAttr->setInvalidAttr();
      }
    }
  }

  if (isa<CXXConstructorDecl>(FD)) {
    Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid)
        << SKEPAttr << diag::InvalidSKEPReason::Constructor;
    SKEPAttr->setInvalidAttr();
  }
  if (isa<CXXDestructorDecl>(FD)) {
    Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid)
        << SKEPAttr << diag::InvalidSKEPReason::Destructor;
    SKEPAttr->setInvalidAttr();
  }
  if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
    if (MD->isExplicitObjectMemberFunction()) {
      Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid)
          << SKEPAttr << diag::InvalidSKEPReason::ExplicitObjectFn;
      SKEPAttr->setInvalidAttr();
    }
  }

  if (FD->isVariadic()) {
    Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid)
        << SKEPAttr << diag::InvalidSKEPReason::VariadicFn;
    SKEPAttr->setInvalidAttr();
  }

  if (FD->isDefaulted()) {
    Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid)
        << SKEPAttr << diag::InvalidSKEPReason::DefaultedFn;
    SKEPAttr->setInvalidAttr();
  } else if (FD->isDeleted()) {
    Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid)
        << SKEPAttr << diag::InvalidSKEPReason::DeletedFn;
    SKEPAttr->setInvalidAttr();
  }

  if (FD->isConsteval()) {
    Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid)
        << SKEPAttr << diag::InvalidSKEPReason::ConstevalFn;
    SKEPAttr->setInvalidAttr();
  } else if (FD->isConstexpr()) {
    Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid)
        << SKEPAttr << diag::InvalidSKEPReason::ConstexprFn;
    SKEPAttr->setInvalidAttr();
  }

  if (FD->isNoReturn()) {
    Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_invalid)
        << SKEPAttr << diag::InvalidSKEPReason::NoreturnFn;
    SKEPAttr->setInvalidAttr();
  }

  if (FD->getReturnType()->isUndeducedType()) {
    Diag(SKEPAttr->getLocation(),
         diag::err_sycl_entry_point_deduced_return_type)
        << SKEPAttr;
    SKEPAttr->setInvalidAttr();
  } else if (!FD->getReturnType()->isDependentType() &&
             !FD->getReturnType()->isVoidType()) {
    Diag(SKEPAttr->getLocation(), diag::err_sycl_entry_point_return_type)
        << SKEPAttr;
    SKEPAttr->setInvalidAttr();
  }

  if (!FD->isInvalidDecl() && !FD->isTemplated() &&
      !SKEPAttr->isInvalidAttr()) {
    const SYCLKernelInfo *SKI =
        getASTContext().findSYCLKernelInfo(SKEPAttr->getKernelName());
    if (SKI) {
      if (!declaresSameEntity(FD, SKI->getKernelEntryPointDecl())) {
        // FIXME: This diagnostic should include the origin of the kernel
        // FIXME: names; not just the locations of the conflicting declarations.
        Diag(FD->getLocation(), diag::err_sycl_kernel_name_conflict)
            << SKEPAttr;
        Diag(SKI->getKernelEntryPointDecl()->getLocation(),
             diag::note_previous_declaration);
        SKEPAttr->setInvalidAttr();
      }
    } else {
      getASTContext().registerSYCLEntryPointFunction(FD);
    }
  }
}

ExprResult SemaSYCL::BuildSYCLKernelLaunchIdExpr(FunctionDecl *FD,
                                                 QualType KNT) {
  // The current context must be the function definition context to ensure
  // that name lookup is performed within the correct scope.
  assert(SemaRef.CurContext == FD && "The current declaration context does not "
                                     "match the requested function context");

  // An appropriate source location is required to emit diagnostics if
  // lookup fails to produce an overload set. The desired location is the
  // start of the function body, but that is not yet available since the
  // body of the function has not yet been set when this function is called.
  // The general location of the function is used instead.
  SourceLocation Loc = FD->getLocation();

  ASTContext &Ctx = SemaRef.getASTContext();
  IdentifierInfo &SYCLKernelLaunchID =
      Ctx.Idents.get("sycl_kernel_launch", tok::TokenKind::identifier);

  // Establish a code synthesis context for the implicit name lookup of
  // a template named 'sycl_kernel_launch'. In the event of an error, this
  // ensures an appropriate diagnostic note is issued to explain why the
  // lookup was performed.
  Sema::CodeSynthesisContext CSC;
  CSC.Kind = Sema::CodeSynthesisContext::SYCLKernelLaunchLookup;
  CSC.Entity = FD;
  Sema::ScopedCodeSynthesisContext ScopedCSC(SemaRef, CSC);

  // Perform ordinary name lookup for a function or variable template that
  // accepts a single type template argument.
  LookupResult Result(SemaRef, &SYCLKernelLaunchID, Loc,
                      Sema::LookupOrdinaryName);
  CXXScopeSpec EmptySS;
  if (SemaRef.LookupTemplateName(Result, SemaRef.getCurScope(), EmptySS,
                                 /*ObjectType*/ QualType(),
                                 /*EnteringContext*/ false,
                                 Sema::TemplateNameIsRequired))
    return ExprError();
  if (Result.isAmbiguous())
    return ExprError();

  TemplateArgumentListInfo TALI{Loc, Loc};
  TemplateArgument KNTA = TemplateArgument(KNT);
  TemplateArgumentLoc TAL =
      SemaRef.getTrivialTemplateArgumentLoc(KNTA, QualType(), Loc);
  TALI.addArgument(TAL);

  ExprResult IdExpr;
  if (SemaRef.isPotentialImplicitMemberAccess(EmptySS, Result,
                                              /*IsAddressOfOperand*/ false)) {
    // The lookup result allows for a possible implicit member access that
    // would require an implicit or explicit 'this' argument.
    IdExpr = SemaRef.BuildPossibleImplicitMemberExpr(
        EmptySS, SourceLocation(), Result, &TALI, SemaRef.getCurScope());
  } else {
    IdExpr = SemaRef.BuildTemplateIdExpr(EmptySS, SourceLocation(), Result,
                                         /*RequiresADL*/ true, &TALI);
  }

  // The resulting expression may be invalid if, for example, 'FD' is a
  // non-static member function and sycl_kernel_launch lookup selects a
  // member function (which would require a 'this' argument which is
  // not available).
  if (IdExpr.isInvalid())
    return ExprError();

  return IdExpr;
}

namespace {

// Constructs the arguments to be passed for the SYCL kernel launch call.
// The first argument is a string literal that contains the SYCL kernel
// name. The remaining arguments are the parameters of 'FD' passed as
// move-elligible xvalues. Returns true on error and false otherwise.
bool BuildSYCLKernelLaunchCallArgs(Sema &SemaRef, FunctionDecl *FD,
                                   const SYCLKernelInfo *SKI,
                                   SmallVectorImpl<Expr *> &Args,
                                   SourceLocation Loc) {
  // The current context must be the function definition context to ensure
  // that parameter references occur within the correct scope.
  assert(SemaRef.CurContext == FD && "The current declaration context does not "
                                     "match the requested function context");

  // Prepare a string literal that contains the kernel name.
  ASTContext &Ctx = SemaRef.getASTContext();
  const std::string &KernelName = SKI->GetKernelName();
  QualType KernelNameCharTy = Ctx.CharTy.withConst();
  llvm::APInt KernelNameSize(Ctx.getTypeSize(Ctx.getSizeType()),
                             KernelName.size() + 1);
  QualType KernelNameArrayTy = Ctx.getConstantArrayType(
      KernelNameCharTy, KernelNameSize, nullptr, ArraySizeModifier::Normal, 0);
  Expr *KernelNameExpr =
      StringLiteral::Create(Ctx, KernelName, StringLiteralKind::Ordinary,
                            /*Pascal*/ false, KernelNameArrayTy, Loc);
  Args.push_back(KernelNameExpr);

  // Forward all parameters of 'FD' to the SYCL kernel launch function as if
  // by std::move().
  for (ParmVarDecl *PVD : FD->parameters()) {
    QualType ParamType = PVD->getOriginalType().getNonReferenceType();
    ExprResult E = SemaRef.BuildDeclRefExpr(PVD, ParamType, VK_LValue, Loc);
    if (E.isInvalid())
      return true;
    if (!PVD->getType()->isLValueReferenceType())
      E = ImplicitCastExpr::Create(SemaRef.Context, E.get()->getType(), CK_NoOp,
                                   E.get(), nullptr, VK_XValue,
                                   FPOptionsOverride());
    if (E.isInvalid())
      return true;
    Args.push_back(E.get());
  }

  return false;
}

// Constructs the SYCL kernel launch call.
StmtResult BuildSYCLKernelLaunchCallStmt(Sema &SemaRef, FunctionDecl *FD,
                                         const SYCLKernelInfo *SKI,
                                         Expr *IdExpr, SourceLocation Loc) {
  SmallVector<Stmt *> Stmts;
  // IdExpr may be null if name lookup failed.
  if (IdExpr) {
    llvm::SmallVector<Expr *, 12> Args;

    // Establish a code synthesis context for construction of the arguments
    // for the implicit call to 'sycl_kernel_launch'.
    {
      Sema::CodeSynthesisContext CSC;
      CSC.Kind = Sema::CodeSynthesisContext::SYCLKernelLaunchLookup;
      CSC.Entity = FD;
      Sema::ScopedCodeSynthesisContext ScopedCSC(SemaRef, CSC);

      if (BuildSYCLKernelLaunchCallArgs(SemaRef, FD, SKI, Args, Loc))
        return StmtError();
    }

    // Establish a code synthesis context for the implicit call to
    // 'sycl_kernel_launch'.
    {
      Sema::CodeSynthesisContext CSC;
      CSC.Kind = Sema::CodeSynthesisContext::SYCLKernelLaunchOverloadResolution;
      CSC.Entity = FD;
      CSC.CallArgs = Args.data();
      CSC.NumCallArgs = Args.size();
      Sema::ScopedCodeSynthesisContext ScopedCSC(SemaRef, CSC);

      ExprResult LaunchResult =
          SemaRef.BuildCallExpr(SemaRef.getCurScope(), IdExpr, Loc, Args, Loc);
      if (LaunchResult.isInvalid())
        return StmtError();

      Stmts.push_back(SemaRef.MaybeCreateExprWithCleanups(LaunchResult).get());
    }
  }

  return CompoundStmt::Create(SemaRef.getASTContext(), Stmts,
                              FPOptionsOverride(), Loc, Loc);
}

// The body of a function declared with the [[sycl_kernel_entry_point]]
// attribute is cloned and transformed to substitute references to the original
// function parameters with references to replacement variables that stand in
// for SYCL kernel parameters or local variables that reconstitute a decomposed
// SYCL kernel argument.
class OutlinedFunctionDeclBodyInstantiator
    : public TreeTransform<OutlinedFunctionDeclBodyInstantiator> {
public:
  using ParmDeclMap = llvm::DenseMap<ParmVarDecl *, VarDecl *>;

  OutlinedFunctionDeclBodyInstantiator(Sema &S, ParmDeclMap &M,
                                       FunctionDecl *FD)
      : TreeTransform<OutlinedFunctionDeclBodyInstantiator>(S), SemaRef(S),
        MapRef(M), FD(FD) {}

  // A new set of AST nodes is always required.
  bool AlwaysRebuild() { return true; }

  // Transform ParmVarDecl references to the supplied replacement variables.
  ExprResult TransformDeclRefExpr(DeclRefExpr *DRE) {
    const ParmVarDecl *PVD = dyn_cast<ParmVarDecl>(DRE->getDecl());
    if (PVD) {
      ParmDeclMap::iterator I = MapRef.find(PVD);
      if (I != MapRef.end()) {
        VarDecl *VD = I->second;
        assert(SemaRef.getASTContext().hasSameUnqualifiedType(
            PVD->getType().getNonReferenceType(), VD->getType()));
        assert(!VD->getType().isMoreQualifiedThan(
            PVD->getType().getNonReferenceType(), SemaRef.getASTContext()));
        VD->setIsUsed();
        return DeclRefExpr::Create(
            SemaRef.getASTContext(), DRE->getQualifierLoc(),
            DRE->getTemplateKeywordLoc(), VD, false, DRE->getNameInfo(),
            DRE->getType(), DRE->getValueKind());
      }
    }
    return DRE;
  }

  // Diagnose CXXThisExpr in a potentially evaluated expression.
  ExprResult TransformCXXThisExpr(CXXThisExpr *CTE) {
    if (SemaRef.currentEvaluationContext().isPotentiallyEvaluated()) {
      SemaRef.Diag(CTE->getExprLoc(), diag::err_sycl_entry_point_invalid_this)
          << (CTE->isImplicitCXXThis() ? /* implicit */ 1 : /* empty */ 0)
          << FD->getAttr<SYCLKernelEntryPointAttr>();
    }
    return CTE;
  }

private:
  Sema &SemaRef;
  ParmDeclMap &MapRef;
  FunctionDecl *FD;
};

OutlinedFunctionDecl *BuildSYCLKernelEntryPointOutline(Sema &SemaRef,
                                                       FunctionDecl *FD,
                                                       CompoundStmt *Body) {
  using ParmDeclMap = OutlinedFunctionDeclBodyInstantiator::ParmDeclMap;
  ParmDeclMap ParmMap;

  OutlinedFunctionDecl *OFD = OutlinedFunctionDecl::Create(
      SemaRef.getASTContext(), FD, FD->getNumParams());
  unsigned i = 0;
  for (ParmVarDecl *PVD : FD->parameters()) {
    ImplicitParamDecl *IPD = ImplicitParamDecl::Create(
        SemaRef.getASTContext(), OFD, SourceLocation(), PVD->getIdentifier(),
        PVD->getType().getNonReferenceType(), ImplicitParamKind::Other);
    OFD->setParam(i, IPD);
    ParmMap[PVD] = IPD;
    ++i;
  }

  OutlinedFunctionDeclBodyInstantiator OFDBodyInstantiator(SemaRef, ParmMap,
                                                           FD);
  Stmt *OFDBody = OFDBodyInstantiator.TransformStmt(Body).get();
  OFD->setBody(OFDBody);
  OFD->setNothrow();

  return OFD;
}

class KernelParamsChecker : public ConstSubobjectVisitor<KernelParamsChecker> {
  SemaSYCL &SemaSYCLRef;
  bool IsValid = true;
  using ObjectAccess =
      llvm::PointerUnion<const ParmVarDecl *, const CXXBaseSpecifier *,
                         const FieldDecl *>;
  SmallVector<ObjectAccess, 4> ObjectAccessPath;

  void emitObjectAccessPathNotes() {
    for (auto Parent : llvm::reverse(ObjectAccessPath)) {
      if (auto *FD = Parent.dyn_cast<const FieldDecl *>()) {
        const CXXRecordDecl *ParentRD = cast<CXXRecordDecl>(FD->getParent());
        if (ParentRD->isLambda()) {
          SemaSYCLRef.Diag(ParentRD->getLocation(), diag::note_within_capture)
              << ParentRD->getCapture(FD->getFieldIndex())->getCapturedVar();
        } else {
          SemaSYCLRef.Diag(ParentRD->getLocation(),
                           diag::note_within_field_of_type)
              << ParentRD;
        }
      } else if (auto *BS = Parent.dyn_cast<const CXXBaseSpecifier *>()) {
        CXXRecordDecl *RD = BS->getType()->getAsCXXRecordDecl();
        assert(RD);
        SemaSYCLRef.Diag(BS->getBeginLoc(), diag::note_within_base_of_type)
            << RD;
      } else {
        auto *Param = cast<const ParmVarDecl *>(Parent);
        SemaSYCLRef.Diag(Param->getBeginLoc(), diag::note_within_param_of_type)
            << Param << Param->getType();
      }
    }
  }

public:
  KernelParamsChecker(SemaSYCL &SR, SourceLocation Loc)
      : ConstSubobjectVisitor<KernelParamsChecker>(SR.getASTContext()),
        SemaSYCLRef(SR) {}

  void checkParameter(const ParmVarDecl *PVD) {
    ObjectAccessPath.push_back(PVD);
    // Check the immediate type of the parameter.
    if (checkType(PVD->getType())) {
      // If type checking wasn't short circuited, visit subobjects to check
      // them.
      visit(PVD->getType());
    }
    ObjectAccessPath.pop_back();
    assert(ObjectAccessPath.empty());
  }

  bool visitBaseSpecifierPre(const CXXBaseSpecifier *BS) {
    ObjectAccessPath.push_back(BS);
    return checkType(BS->getType());
  }

  bool visitFieldDeclPre(const FieldDecl *FD) {
    ObjectAccessPath.push_back(FD);
    return checkType(FD->getType());
  }

  // Returns true if subobjects should be visited and false otherwise.
  bool checkType(QualType Ty) {
    if (Ty->isReferenceType()) {
      auto DirectParent = ObjectAccessPath.back();
      // Reference cannot be a base, so just assume we came via a FieldDecl.
      if (isa<const ParmVarDecl *>(DirectParent)) {
        // If reference is a kernel parameter, there is nothing to do. We allow
        // references in direct kernel parameters for better performance of the
        // host code and we eliminate them when building actual kernel.
        return true;
      }

      auto *DirectFieldParent = cast<const FieldDecl *>(DirectParent);
      SemaSYCLRef.Diag(DirectFieldParent->getLocation(),
                       diag::err_bad_kernel_param_type)
          << DirectFieldParent->getType();
      emitObjectAccessPathNotes();

      // Don't visit the type of the reference since any further invalid
      // kernel parameter types contained within the referenced type
      // might not be relevant once the programmer addresses the
      // invalid use of a reference.
      IsValid = false;
      return false;
    }
    return true;
  }

  void visitFieldDeclPost(const FieldDecl *FD) { ObjectAccessPath.pop_back(); }
  void visitBaseSpecifierPost(const CXXBaseSpecifier *BS) {
    ObjectAccessPath.pop_back();
  }

  bool isInvalid() { return !IsValid; }
};

bool verifyKernelParams(FunctionDecl *FD, SemaSYCL &SemaSYCLRef) {
  KernelParamsChecker KAC(SemaSYCLRef, FD->getLocation());
  for (auto Param : FD->parameters())
    KAC.checkParameter(Param);
  return KAC.isInvalid();
}

} // unnamed namespace

StmtResult SemaSYCL::BuildSYCLKernelCallStmt(FunctionDecl *FD,
                                             CompoundStmt *Body,
                                             Expr *LaunchIdExpr) {
  assert(!FD->isInvalidDecl());
  assert(!FD->isTemplated());
  assert(FD->hasPrototype());
  // The current context must be the function definition context to ensure
  // that name lookup and parameter and local variable creation are performed
  // within the correct scope.
  assert(SemaRef.CurContext == FD && "The current declaration context does not "
                                     "match the requested function context");

  const auto *SKEPAttr = FD->getAttr<SYCLKernelEntryPointAttr>();
  assert(SKEPAttr && "Missing sycl_kernel_entry_point attribute");
  assert(!SKEPAttr->isInvalidAttr() &&
         "sycl_kernel_entry_point attribute is invalid");

  // Ensure that the kernel name was previously registered and that the
  // stored declaration matches.
  const SYCLKernelInfo &SKI =
      getASTContext().getSYCLKernelInfo(SKEPAttr->getKernelName());
  assert(declaresSameEntity(SKI.getKernelEntryPointDecl(), FD) &&
         "SYCL kernel name conflict");
  if (verifyKernelParams(FD, *this))
    return StmtError();

  // Build the outline of the synthesized device entry point function.
  OutlinedFunctionDecl *OFD =
      BuildSYCLKernelEntryPointOutline(SemaRef, FD, Body);
  assert(OFD);

  // Build the host kernel launch statement. An appropriate source location
  // is required to emit diagnostics.
  SourceLocation Loc = Body->getLBracLoc();
  StmtResult LaunchResult =
      BuildSYCLKernelLaunchCallStmt(SemaRef, FD, &SKI, LaunchIdExpr, Loc);
  if (LaunchResult.isInvalid())
    return StmtError();

  Stmt *NewBody =
      new (getASTContext()) SYCLKernelCallStmt(Body, LaunchResult.get(), OFD);

  return NewBody;
}

StmtResult SemaSYCL::BuildUnresolvedSYCLKernelCallStmt(CompoundStmt *Body,
                                                       Expr *LaunchIdExpr) {
  return UnresolvedSYCLKernelCallStmt::Create(SemaRef.getASTContext(), Body,
                                              LaunchIdExpr);
}
