diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -294,6 +294,7 @@ def MicrosoftExt : LangOpt<"MicrosoftExt">; def Borland : LangOpt<"Borland">; def CUDA : LangOpt<"CUDA">; +def SYCL : LangOpt<"SYCLIsDevice">; def COnly : LangOpt<"COnly", "!LangOpts.CPlusPlus">; def CPlusPlus : LangOpt<"CPlusPlus">; def OpenCL : LangOpt<"OpenCL">; @@ -1007,6 +1008,20 @@ let Documentation = [Undocumented]; } +def SYCLDevice : InheritableAttr { + let Spellings = []; + let Subjects = SubjectList<[Function, Var]>; + let LangOpts = [SYCL]; + let Documentation = [Undocumented]; +} + +def SYCLKernel : InheritableAttr { + let Spellings = [Clang<"sycl_kernel">]; + let Subjects = SubjectList<[Function]>; + let LangOpts = [SYCL]; + let Documentation = [SYCLKernelDocs]; +} + def C11NoReturn : InheritableAttr { let Spellings = [Keyword<"_Noreturn">]; let Subjects = SubjectList<[Function], ErrorDiag>; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -253,6 +253,41 @@ }]; } +def SYCLKernelDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The ``sycl_kernel`` attribute specifies that a function is SYCL "kernel +function". SYCL "kernel function" defines an entry point to a kernel. +Kernel is a function which will be compiled for the device and defines some +entry point to device code i.e. will be called by host in run time. +Here is a code example of the SYCL program, which demonstrates the need for +this attribute: +.. code-block:: c++ + + int foo(int x) { return ++x; } + + using namespace cl::sycl; + queue Q; + buffer a(range<1>{1024}); + Q.submit([&](handler& cgh) { + auto A = a.get_access(cgh); + cgh.parallel_for(range<1>{1024}, [=](id<1> index) { + A[index] = index[0] * 2 + index[1] + foo(42); + }); + } +The lambda that is passed to the ``parallel_for`` is called SYCL +"kernel function". +The SYCL Runtime implementation will use ``sycl_kernel`` attribute to mark this +lambda as SYCL "kernel function". Compiler is supposed to construct a kernel +from "kernel function", add it to the "device part" of code and traverse all +symbols accessible from "kernel function" and add them to the "device part" of +the code. In this code example compiler is supposed to add "foo" function to the +"device part" of the code. +More details can be found in the SYCL 1.2.1 specification, Sections 4.8.9 and +6.4. + }]; +} + def C11NoReturnDocs : Documentation { let Category = DocCatFunction; let Content = [{ diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -11192,6 +11192,29 @@ ConstructorDestructor, BuiltinFunction }; + +private: + /// Contains function declarations to be added to the SYCL device code. + /// In SYCL, when we generate device code, we don't know which functions we + /// will emit before we emit sycl kernels, so we add device functions to this + /// array and handle it in separate way. + SmallVector SyclDeviceFunctions; + +public: + /// This function adds the function declaration to the SYCL device code. + void addSyclDeviceFunc(Decl *D) { + D->addAttr(SYCLDeviceAttr::CreateImplicit(Context)); + SyclDeviceFunctions.push_back(D); + } + /// Access to SYCL device function decls. + SmallVectorImpl &syclDeviceFuncs() { return SyclDeviceFunctions; } + + /// Constructs OpenCL kernel from the SYCL "kernel function" and adds it to + /// the SYCL device code. + void ConstructSYCLKernel(FunctionDecl *KernelCallerFunc); + /// This function marks all functions accessible from SYCL kernels with the + /// SYCL device attribute and adds them to the SYCL device code. + void MarkDevice(void); }; /// RAII object that enters a new expression evaluation context. diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -2405,6 +2405,11 @@ if (Global->hasAttr()) return emitIFuncDefinition(GD); + // If this is SYCL device, only emit declarations marked with the SYCL device + // attribute. + if (LangOpts.SYCLIsDevice && !Global->hasAttr()) + return; + // If this is a cpu_dispatch multiversion function, emit the resolver. if (Global->hasAttr()) return emitCPUDispatchDefinition(GD); @@ -2519,6 +2524,10 @@ // The value must be emitted, but cannot be emitted eagerly. assert(!MayBeEmittedEagerly(Global)); addDeferredDeclToEmit(GD); + } else if (LangOpts.SYCLIsDevice) { + // SYCL kernels can be templated and not called from anywhere in the + // module but should be emitted. + addDeferredDeclToEmit(GD); } else { // Otherwise, remember that we saw a deferred decl with this name. The // first use of the mangled name will cause it to move into diff --git a/clang/lib/Parse/ParseAST.cpp b/clang/lib/Parse/ParseAST.cpp --- a/clang/lib/Parse/ParseAST.cpp +++ b/clang/lib/Parse/ParseAST.cpp @@ -168,6 +168,12 @@ for (Decl *D : S.WeakTopLevelDecls()) Consumer->HandleTopLevelDecl(DeclGroupRef(D)); + if (S.getLangOpts().SYCLIsDevice) { + for (Decl *D : S.syclDeviceFuncs()) { + Consumer->HandleTopLevelDecl(DeclGroupRef(D)); + } + } + Consumer->HandleTranslationUnit(S.getASTContext()); // Finalize the template instantiation observer chain. diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -56,6 +56,7 @@ SemaStmt.cpp SemaStmtAsm.cpp SemaStmtAttr.cpp + SemaSYCL.cpp SemaTemplate.cpp SemaTemplateDeduction.cpp SemaTemplateInstantiate.cpp diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -905,6 +905,9 @@ PerformPendingInstantiations(); } + if (getLangOpts().SYCLIsDevice) + MarkDevice(); + assert(LateParsedInstantiations.empty() && "end of TU template instantiation should not create more " "late-parsed templates"); diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -6767,6 +6767,9 @@ case ParsedAttr::AT_Flatten: handleSimpleAttribute(S, D, AL); break; + case ParsedAttr::AT_SYCLKernel: + handleSimpleAttribute(S, D, AL); + break; case ParsedAttr::AT_Format: handleFormatAttr(S, D, AL); break; diff --git a/clang/lib/Sema/SemaSYCL.cpp b/clang/lib/Sema/SemaSYCL.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Sema/SemaSYCL.cpp @@ -0,0 +1,74 @@ +//===- 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/AST/RecursiveASTVisitor.h" +#include "clang/Sema/Sema.h" + +using namespace clang; + +class MarkDeviceFunction : public RecursiveASTVisitor { +public: + MarkDeviceFunction(Sema &S) + : RecursiveASTVisitor(), SemaRef(S) {} + + bool VisitCallExpr(CallExpr *E) { + if (FunctionDecl *Callee = E->getDirectCallee()) { + Callee = Callee->getCanonicalDecl(); + // 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. + if (FunctionDecl *Def = Callee->getDefinition()) { + if (!Def->hasAttr()) { + SemaRef.addSyclDeviceFunc(Def); + this->TraverseStmt(Def->getBody()); + } + } + } + return true; + } + + bool VisitCXXConstructExpr(CXXConstructExpr *E) { + CXXConstructorDecl *Ctor = E->getConstructor(); + + if (FunctionDecl *Def = Ctor->getDefinition()) + SemaRef.addSyclDeviceFunc(Def); + + const CXXRecordDecl *ConstructedType = Ctor->getParent(); + if (ConstructedType->hasUserDeclaredDestructor()) { + CXXDestructorDecl *Dtor = ConstructedType->getDestructor(); + + if (FunctionDecl *Def = Dtor->getDefinition()) + SemaRef.addSyclDeviceFunc(Def); + } + return true; + } + +private: + Sema &SemaRef; +}; + +void Sema::ConstructSYCLKernel(FunctionDecl *KernelCallerFunc) { + addSyclDeviceFunc(KernelCallerFunc); +} + +void Sema::MarkDevice(void) { + // Let's mark all called functions with the SYCL Device attribute. + MarkDeviceFunction Marker(*this); + for (const auto &Elt : syclDeviceFuncs()) { + if (auto *Func = dyn_cast(Elt)) { + if (FunctionDecl *Def = Func->getDefinition()) { + if (!Def->hasAttr()) + addSyclDeviceFunc(Def); + Marker.TraverseStmt(Def->getBody()); + } + } + } +} diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -5532,14 +5532,30 @@ Function, [this, Inst, DefinitionRequired](FunctionDecl *CurFD) { InstantiateFunctionDefinition(/*FIXME:*/ Inst.second, CurFD, true, DefinitionRequired, true); - if (CurFD->isDefined()) + if (CurFD->isDefined()) { + // Because all SYCL kernel functions are template functions - + // they have deferred instantination. We need bodies of these + // functions so we are checking for the SYCL kernel attribute + // after instantination. + if (getLangOpts().SYCLIsDevice && + CurFD->hasAttr()) + ConstructSYCLKernel(CurFD); CurFD->setInstantiationIsPending(false); + } }); } else { InstantiateFunctionDefinition(/*FIXME:*/ Inst.second, Function, true, DefinitionRequired, true); - if (Function->isDefined()) + if (Function->isDefined()) { + // Because all SYCL kernel functions are template functions - they + // have deferred instantination. We need bodies of these functions + // so we are checking for the SYCL kernel attribute after + // instantination. + if (getLangOpts().SYCLIsDevice && + Function->hasAttr()) + ConstructSYCLKernel(Function); Function->setInstantiationIsPending(false); + } } continue; } diff --git a/clang/test/CodeGenSYCL/device-functions.cpp b/clang/test/CodeGenSYCL/device-functions.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenSYCL/device-functions.cpp @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -triple spir64-unknown-unknown -std=c++11 -fsycl-is-device -S -emit-llvm %s -o - | FileCheck %s + +template +T bar(T arg); + +void foo() { + int a = 1 + 1 + bar(1); +} + +template +T bar(T arg) { + return arg; +} + +template +__attribute__((sycl_kernel)) void kernel_single_task(Func kernelFunc) { + kernelFunc(); +} + +int main() { + kernel_single_task([]() { foo(); }); + return 0; +} +// CHECK: define spir_func void @{{.*}}foo +// CHECK: define linkonce_odr spir_func i32 @{{.*}}bar +// CHECK: define internal spir_func void @{{.*}}kernel_single_task +// FIXME: Next function is lambda () operator. spir_func calling convention +// is missed for C++ methods. +// CHECK: define internal void @"_ZZ4mainENK3$_0clEv"(%class.anon* %this) diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -125,6 +125,7 @@ // CHECK-NEXT: ReturnTypestate (SubjectMatchRule_function, SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: ReturnsNonNull (SubjectMatchRule_objc_method, SubjectMatchRule_function) // CHECK-NEXT: ReturnsTwice (SubjectMatchRule_function) +// CHECK-NEXT: SYCLKernel (SubjectMatchRule_function) // CHECK-NEXT: ScopedLockable (SubjectMatchRule_record) // CHECK-NEXT: Section (SubjectMatchRule_function, SubjectMatchRule_variable_is_global, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property) // CHECK-NEXT: SetTypestate (SubjectMatchRule_function_is_member) diff --git a/clang/test/SemaSYCL/device-attributes-on-non-sycl.cpp b/clang/test/SemaSYCL/device-attributes-on-non-sycl.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaSYCL/device-attributes-on-non-sycl.cpp @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -std=c++11 -fsyntax-only -fsycl-is-device -verify %s +// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify -x c++ %s + +#ifndef __SYCL_DEVICE_ONLY__ +// expected-warning@+6 {{'sycl_kernel' attribute ignored}} +// expected-warning@+6 {{'sycl_kernel' attribute ignored}} +#else +// expected-no-diagnostics +#endif + +__attribute__((sycl_kernel)) void foo(); +[[clang::sycl_kernel]] void foo2(); + diff --git a/clang/test/SemaSYCL/device-attributes.cpp b/clang/test/SemaSYCL/device-attributes.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaSYCL/device-attributes.cpp @@ -0,0 +1,10 @@ +// RUN: %clang_cc1 -std=c++11 -fsyntax-only -fsycl-is-device -verify %s + +[[clang::sycl_kernel]] int gv2 = 0; // expected-warning {{'sycl_kernel' attribute only applies to functions}} +__attribute__((sycl_kernel)) int gv3 = 0; // expected-warning {{'sycl_kernel' attribute only applies to functions}} + +__attribute__((sycl_kernel)) void foo(); +[[clang::sycl_kernel]] void foo1(); + +__attribute__((sycl_kernel(1))) void foo(); // expected-error {{'sycl_kernel' attribute takes no arguments}} +[[clang::sycl_kernel(1)]] void foo2(); // expected-error {{'sycl_kernel' attribute takes no arguments}} diff --git a/clang/test/SemaSYCL/device-code-outlining.cpp b/clang/test/SemaSYCL/device-code-outlining.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaSYCL/device-code-outlining.cpp @@ -0,0 +1,37 @@ +// RUN: %clang_cc1 -std=c++11 -fsycl-is-device -ast-dump %s | FileCheck %s + +template +T bar(T arg); +// CHECK: FunctionTemplateDecl {{.*}} bar +// CHECK: SYCLDeviceAttr {{.*}} Implicit + +void foo() { + int a = 1 + 1 + bar(1); +} +// CHECK: FunctionDecl {{.*}} foo +// CHECK: SYCLDeviceAttr {{.*}} Implicit + +template +T bar(T arg) { + return arg; +} + +template +__attribute__((sycl_kernel)) void kernel_single_task(Func kernelFunc) { + kernelFunc(); +} +// CHECK: FunctionTemplateDecl {{.*}} kernel_single_task +// CHECK: SYCLDeviceAttr {{.*}} Implicit + +void host_foo() { + int b = 0; +} +// CHECK: FunctionDecl {{.*}} host_foo +// CHECK-NOT: SYCLDeviceAttr +// CHECK: FunctionDecl {{.*}} main + +int main() { + kernel_single_task([]() { foo(); }); + host_foo(); + return 0; +}