Skip to content

Commit

Permalink
[SYCL] Implement virtual-table prohibit in SYCL device code.
Browse files Browse the repository at this point in the history
According to SYCL specification, virtual tables are illegal in device code. This
accomplishes that in a couple of ways.  First, there is a code-gen
assert that will prevent emission of virtual tables always. Second, it
prevents virtual tables from being 'used', so non-SYCL kernel code
cannot cause v-tables to be emitted.  Finally, SYCL-specific functions
are checked for usage of polymorphic functions and a diagnostic is
emitted.

Signed-off-by: Vladimir Lazarev <vladimir.lazarev@intel.com>
  • Loading branch information
vladimirlaz committed Jan 22, 2019
1 parent 33ff936 commit 08b20c1
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 4 deletions.
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -9488,4 +9488,7 @@ def err_sycl_attribute_address_space_invalid : Error<
def err_sycl_kernel_name_class_not_top_level : Error<
"kernel name class and its template argument classes' declarations can only "
"nest in a namespace: %0">;
def err_sycl_virtual_types : Error<
"No class with a vtable can be used in a SYCL kernel or any code included in the kernel">;
def note_sycl_used_here : Note<"used here">;
} // end of sema component.
4 changes: 4 additions & 0 deletions clang/lib/CodeGen/CodeGenTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,10 @@ llvm::StructType *CodeGenTypes::ConvertRecordDeclType(const RecordDecl *RD) {
return Ty;
}

assert((!Context.getLangOpts().SYCL || !isa<CXXRecordDecl>(RD) ||
!dyn_cast<CXXRecordDecl>(RD)->isPolymorphic()) &&
"Types with virtual functions not allowed in SYCL");

// Okay, this is a definition of a type. Compile the implementation now.
bool InsertResult = RecordsBeingLaidOut.insert(Key).second;
(void)InsertResult;
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15013,6 +15013,10 @@ void Sema::MarkVTableUsed(SourceLocation Loc, CXXRecordDecl *Class,
return;
}

// No VTable usage is legal in SYCL, so don't bother marking them used.
if (getLangOpts().SYCL)
return;

// Try to insert this class into the map.
LoadExternalVTableUses();
Class = Class->getCanonicalDecl();
Expand Down
94 changes: 90 additions & 4 deletions clang/lib/Sema/SemaSYCL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,27 +49,113 @@ class MarkDeviceFunction : public RecursiveASTVisitor<MarkDeviceFunction> {
MarkDeviceFunction(Sema &S)
: RecursiveASTVisitor<MarkDeviceFunction>(), SemaRef(S) {}
bool VisitCallExpr(CallExpr *e) {
for (const auto &Arg : e->arguments())
CheckTypeForVirtual(Arg->getType(), Arg->getSourceRange());

if (FunctionDecl *Callee = e->getDirectCallee()) {
// Remember that all SYCL kernel functions have deferred
// instantiation as template functions. It means that
// all functions used by kernel have already been parsed and have
// definitions.

CheckTypeForVirtual(Callee->getReturnType(), Callee->getSourceRange());

if (FunctionDecl *Def = Callee->getDefinition()) {
if (!Def->hasAttr<SYCLDeviceAttr>()) {
Def->addAttr(SYCLDeviceAttr::CreateImplicit(SemaRef.Context));
this->TraverseStmt(Def->getBody());
// But because parser works with top level declarations and CodeGen
// already saw and ignored our function without device attribute we
// need to add this function into SYCL kernels array to show it
// this function again.
SemaRef.AddSyclKernel(Def);
}
}
}
return true;
}

bool VisitCXXConstructExpr(CXXConstructExpr *E) {
for (const auto &Arg : E->arguments())
CheckTypeForVirtual(Arg->getType(), Arg->getSourceRange());

CXXConstructorDecl *Ctor = E->getConstructor();

if (FunctionDecl *Def = Ctor->getDefinition()) {
Def->addAttr(SYCLDeviceAttr::CreateImplicit(SemaRef.Context));
this->TraverseStmt(Def->getBody());
SemaRef.AddSyclKernel(Def);
}

const auto *ConstructedType = Ctor->getParent();
if (ConstructedType->hasUserDeclaredDestructor()) {
CXXDestructorDecl *Dtor = ConstructedType->getDestructor();

if (FunctionDecl *Def = Dtor->getDefinition()) {
Def->addAttr(SYCLDeviceAttr::CreateImplicit(SemaRef.Context));
this->TraverseStmt(Def->getBody());
SemaRef.AddSyclKernel(Def);
}
}
return true;
}

bool VisitTypedefNameDecl(TypedefNameDecl *TD) {
CheckTypeForVirtual(TD->getUnderlyingType(), TD->getLocation());
return true;
}

bool VisitRecordDecl(RecordDecl *RD) {
CheckTypeForVirtual(QualType{RD->getTypeForDecl(), 0}, RD->getLocation());
return true;
}

bool VisitParmVarDecl(VarDecl *VD) {
CheckTypeForVirtual(VD->getType(), VD->getLocation());
return true;
}

bool VisitVarDecl(VarDecl *VD) {
CheckTypeForVirtual(VD->getType(), VD->getLocation());
return true;
}

bool VisitDeclRefExpr(DeclRefExpr *E) {
CheckTypeForVirtual(E->getType(), E->getSourceRange());
return true;
}

private:
bool CheckTypeForVirtual(QualType Ty, SourceRange Loc) {
while (Ty->isAnyPointerType() || Ty->isArrayType())
Ty = QualType{Ty->getPointeeOrArrayElementType(), 0};

if (const auto *CRD = Ty->getAsCXXRecordDecl()) {
if (CRD->isPolymorphic()) {
SemaRef.Diag(CRD->getLocation(), diag::err_sycl_virtual_types);
SemaRef.Diag(Loc.getBegin(), diag::note_sycl_used_here);
return false;
}

for (const auto &Field : CRD->fields()) {
if (!CheckTypeForVirtual(Field->getType(), Field->getSourceRange())) {
SemaRef.Diag(Loc.getBegin(), diag::note_sycl_used_here);
return false;
}
}
} else if (const auto *RD = Ty->getAsRecordDecl()) {
for (const auto &Field : RD->fields()) {
if (!CheckTypeForVirtual(Field->getType(), Field->getSourceRange())) {
SemaRef.Diag(Loc.getBegin(), diag::note_sycl_used_here);
return false;
}
}
} else if (const auto *FPTy = dyn_cast<FunctionProtoType>(Ty)) {
for (const auto &ParamTy : FPTy->param_types())
if (!CheckTypeForVirtual(ParamTy, Loc))
return false;
return CheckTypeForVirtual(FPTy->getReturnType(), Loc);
} else if (const auto *FTy = dyn_cast<FunctionType>(Ty)) {
return CheckTypeForVirtual(FTy->getReturnType(), Loc);
}
return true;
}
Sema &SemaRef;
};

Expand Down
30 changes: 30 additions & 0 deletions clang/test/SemaSYCL/no-vtables.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// RUN: %clang_cc1 -fsycl-is-device -verify -fsyntax-only -x c++ -emit-llvm-only %s
// expected-no-diagnostics
// Should never fail, since the type is never used in kernel code.

struct Base {
virtual void f(){}
};

struct Inherit : Base {
virtual void f() override {}
};

void always_uses() {
Inherit u;
}

void usage() {
}


template <typename name, typename Func>
__attribute__((sycl_kernel)) void kernel_single_task(Func kernelFunc) {
kernelFunc();
}
int main() {
always_uses();
kernel_single_task<class fake_kernel>([]() { usage(); });
return 0;
}

49 changes: 49 additions & 0 deletions clang/test/SemaSYCL/no-vtables2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// RUN: %clang_cc1 -fsycl-is-device -Wno-return-type -verify -fsyntax-only -x c++ -emit-llvm-only %s

struct Base {
virtual void f() const {}
};

// expected-error@+1 9{{No class with a vtable can be used in a SYCL kernel or any code included in the kernel}}
struct Inherit : Base {
virtual void f() const override {}
};

Inherit always_uses() {
Inherit u;
}

static constexpr Inherit IH;

// expected-note@+1{{used here}}
Inherit *usage_child(){}

// expected-note@+1{{used here}}
Inherit usage() {
// expected-note@+1{{used here}}
Inherit u;
// expected-note@+1{{used here}}
Inherit *u_ptr;

// expected-note@+1{{used here}}
using foo = Inherit;
// expected-note@+1{{used here}}
typedef Inherit bar;
// expected-note@+1{{used here}}
IH.f();

// expected-note@+1{{used here}}
usage_child();
}


template <typename name, typename Func>
__attribute__((sycl_kernel)) void kernel_single_task(Func kernelFunc) {
kernelFunc();
}
int main() {
// expected-note@+1{{used here}}
kernel_single_task<class fake_kernel>([]() { usage(); });
return 0;
}

42 changes: 42 additions & 0 deletions clang/test/SemaSYCL/no-vtables3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// RUN: %clang_cc1 -fsycl-is-device -Wno-return-type -verify -fsyntax-only -x c++ -emit-llvm-only %s

struct Base {
virtual void f() const {}
};

// expected-error@+1 3{{No class with a vtable can be used in a SYCL kernel or any code included in the kernel}}
struct Inherit : Base {
virtual void f() const override {}
};

struct Wrapper{
Wrapper() {
// expected-note@+1{{used here}}
Inherit IH;
}

void Func() {
// expected-note@+1{{used here}}
Inherit IH;
}

~Wrapper() {
// expected-note@+1{{used here}}
Inherit IH;
}
};

void usage() {
Wrapper WR;
WR.Func();
}

template <typename name, typename Func>
__attribute__((sycl_kernel)) void kernel_single_task(Func kernelFunc) {
kernelFunc();
}
int main() {
kernel_single_task<class fake_kernel>([]() { usage(); });
return 0;
}

30 changes: 30 additions & 0 deletions clang/test/SemaSYCL/no-vtables4.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// RUN: %clang_cc1 -fsycl-is-device -Wno-return-type -verify -fsyntax-only -x c++ -emit-llvm-only %s

struct Base {
virtual void f() const {}
};

// expected-error@+1{{No class with a vtable can be used in a SYCL kernel or any code included in the kernel}}
struct Inherit : Base {
virtual void f() const override {}
};

struct Wrapper{
// expected-note@+1{{used here}}
Inherit I;
};

void usage() {
// expected-note@+1{{used here}}
Wrapper WR;
}

template <typename name, typename Func>
__attribute__((sycl_kernel)) void kernel_single_task(Func kernelFunc) {
kernelFunc();
}
int main() {
kernel_single_task<class fake_kernel>([]() { usage(); });
return 0;
}

0 comments on commit 08b20c1

Please sign in to comment.