blob: b942f19761f4010572784fffd3f30ff1b5329766 [file] [edit]
//===- 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);
}