[CUDA] Reject calls to __device__ functions from host variable global initializers.

Reviewers: tra

Subscribers: cfe-commits

Differential Revision: https://reviews.llvm.org/D23335

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@278196 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Justin Lebar 2016-08-10 01:09:21 +00:00
parent 8830dfce4f
commit bd12a41e3d
3 changed files with 81 additions and 27 deletions

View File

@ -6640,6 +6640,9 @@ def err_global_call_not_config : Error<
def err_ref_bad_target : Error<
"reference to %select{__device__|__global__|__host__|__host__ __device__}0 "
"function %1 in %select{__device__|__global__|__host__|__host__ __device__}2 function">;
def err_ref_bad_target_global_initializer : Error<
"reference to %select{__device__|__global__|__host__|__host__ __device__}0 "
"function %1 in global initializer">;
def warn_kern_is_method : Extension<
"kernel function %0 is a member function; this may not be accepted by nvcc">,
InGroup<CudaCompat>;

View File

@ -10728,36 +10728,55 @@ Sema::FinalizeDeclaration(Decl *ThisDecl) {
// 7.5). We must also apply the same checks to all __shared__
// variables whether they are local or not. CUDA also allows
// constant initializers for __constant__ and __device__ variables.
if (getLangOpts().CUDA && getLangOpts().CUDAIsDevice) {
if (getLangOpts().CUDA) {
const Expr *Init = VD->getInit();
if (Init && VD->hasGlobalStorage() &&
(VD->hasAttr<CUDADeviceAttr>() || VD->hasAttr<CUDAConstantAttr>() ||
VD->hasAttr<CUDASharedAttr>())) {
assert((!VD->isStaticLocal() || VD->hasAttr<CUDASharedAttr>()));
bool AllowedInit = false;
if (const CXXConstructExpr *CE = dyn_cast<CXXConstructExpr>(Init))
AllowedInit =
isEmptyCudaConstructor(VD->getLocation(), CE->getConstructor());
// We'll allow constant initializers even if it's a non-empty
// constructor according to CUDA rules. This deviates from NVCC,
// but allows us to handle things like constexpr constructors.
if (!AllowedInit &&
(VD->hasAttr<CUDADeviceAttr>() || VD->hasAttr<CUDAConstantAttr>()))
AllowedInit = VD->getInit()->isConstantInitializer(
Context, VD->getType()->isReferenceType());
// Also make sure that destructor, if there is one, is empty.
if (AllowedInit)
if (CXXRecordDecl *RD = VD->getType()->getAsCXXRecordDecl())
if (Init && VD->hasGlobalStorage()) {
if (VD->hasAttr<CUDADeviceAttr>() || VD->hasAttr<CUDAConstantAttr>() ||
VD->hasAttr<CUDASharedAttr>()) {
assert((!VD->isStaticLocal() || VD->hasAttr<CUDASharedAttr>()));
bool AllowedInit = false;
if (const CXXConstructExpr *CE = dyn_cast<CXXConstructExpr>(Init))
AllowedInit =
isEmptyCudaDestructor(VD->getLocation(), RD->getDestructor());
isEmptyCudaConstructor(VD->getLocation(), CE->getConstructor());
// We'll allow constant initializers even if it's a non-empty
// constructor according to CUDA rules. This deviates from NVCC,
// but allows us to handle things like constexpr constructors.
if (!AllowedInit &&
(VD->hasAttr<CUDADeviceAttr>() || VD->hasAttr<CUDAConstantAttr>()))
AllowedInit = VD->getInit()->isConstantInitializer(
Context, VD->getType()->isReferenceType());
if (!AllowedInit) {
Diag(VD->getLocation(), VD->hasAttr<CUDASharedAttr>()
? diag::err_shared_var_init
: diag::err_dynamic_var_init)
<< Init->getSourceRange();
VD->setInvalidDecl();
// Also make sure that destructor, if there is one, is empty.
if (AllowedInit)
if (CXXRecordDecl *RD = VD->getType()->getAsCXXRecordDecl())
AllowedInit =
isEmptyCudaDestructor(VD->getLocation(), RD->getDestructor());
if (!AllowedInit) {
Diag(VD->getLocation(), VD->hasAttr<CUDASharedAttr>()
? diag::err_shared_var_init
: diag::err_dynamic_var_init)
<< Init->getSourceRange();
VD->setInvalidDecl();
}
} else {
// This is a host-side global variable. Check that the initializer is
// callable from the host side.
const FunctionDecl *InitFn = nullptr;
if (const CXXConstructExpr *CE = dyn_cast<CXXConstructExpr>(Init)) {
InitFn = CE->getConstructor();
} else if (const CallExpr *CE = dyn_cast<CallExpr>(Init)) {
InitFn = CE->getDirectCallee();
}
if (InitFn) {
CUDAFunctionTarget InitFnTarget = IdentifyCUDATarget(InitFn);
if (InitFnTarget != CFT_Host && InitFnTarget != CFT_HostDevice) {
Diag(VD->getLocation(), diag::err_ref_bad_target_global_initializer)
<< InitFnTarget << InitFn;
Diag(InitFn->getLocation(), diag::note_previous_decl) << InitFn;
VD->setInvalidDecl();
}
}
}
}
}

View File

@ -0,0 +1,32 @@
// RUN: %clang_cc1 %s --std=c++11 -triple x86_64-linux-unknown -fsyntax-only -o - -verify
#include "Inputs/cuda.h"
// Check that we get an error if we try to call a __device__ function from a
// module initializer.
struct S {
__device__ S() {}
// expected-note@-1 {{'S' declared here}}
};
S s;
// expected-error@-1 {{reference to __device__ function 'S' in global initializer}}
struct T {
__host__ __device__ T() {}
};
T t; // No error, this is OK.
struct U {
__host__ U() {}
__device__ U(int) {}
// expected-note@-1 {{'U' declared here}}
};
U u(42);
// expected-error@-1 {{reference to __device__ function 'U' in global initializer}}
__device__ int device_fn() { return 42; }
// expected-note@-1 {{'device_fn' declared here}}
int n = device_fn();
// expected-error@-1 {{reference to __device__ function 'device_fn' in global initializer}}