Skip to content

Commit a3b340b

Browse files
authored
[SYCL][UX] Diagnostic for undefined device functions (#1026)
Brand new diagnostic introduced: "call an undefined function w/o SYCL_EXTERNAL attribute" The diagnostic is displayed for functions definitions of which are not available when its declaration is not marked with SYCL_EXTERNAL attribute if and only if the method is found in call graph (i.e. it will be called during runtime). Signed-off-by: Sergey Kanaev <[email protected]>
1 parent 1da6fbe commit a3b340b

30 files changed

+1213
-582
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10642,7 +10642,9 @@ def err_sycl_restrict : Error<
1064210642
"|allocate storage"
1064310643
"|use inline assembly"
1064410644
"|call a dllimport function"
10645-
"|call a variadic function}0">;
10645+
"|call a variadic function"
10646+
"|call an undefined function without SYCL_EXTERNAL attribute"
10647+
"}0">;
1064610648
def err_sycl_virtual_types : Error<
1064710649
"No class with a vtable can be used in a SYCL kernel or any code included in the kernel">;
1064810650
def note_sycl_used_here : Note<"used here">;

clang/include/clang/Sema/Sema.h

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12298,11 +12298,13 @@ class Sema final {
1229812298
KernelAllocateStorage,
1229912299
KernelUseAssembly,
1230012300
KernelCallDllimportFunction,
12301-
KernelCallVariadicFunction
12302-
};
12301+
KernelCallVariadicFunction,
12302+
KernelCallUndefinedFunction
12303+
};
12304+
1230312305
bool isKnownGoodSYCLDecl(const Decl *D);
1230412306
void ConstructOpenCLKernel(FunctionDecl *KernelCallerFunc, MangleContext &MC);
12305-
void MarkDevice(void);
12307+
void MarkDevice();
1230612308

1230712309
/// Creates a DeviceDiagBuilder that emits the diagnostic if the current
1230812310
/// context is "used as device code".
@@ -12323,10 +12325,25 @@ class Sema final {
1232312325
/// SYCLDiagIfDeviceCode(Loc, diag::err_thread_unsupported);
1232412326
DeviceDiagBuilder SYCLDiagIfDeviceCode(SourceLocation Loc, unsigned DiagID);
1232512327

12326-
/// Checks if Callee function is a device function and emits
12327-
/// diagnostics if it is known that it is a device function, adds this
12328-
/// function to the DeviceCallGraph otherwise.
12329-
void checkSYCLDeviceFunction(SourceLocation Loc, FunctionDecl *Callee);
12328+
/// Check whether we're allowed to call Callee from the current context.
12329+
///
12330+
/// - If the call is never allowed in a semantically-correct program
12331+
/// emits an error and returns false.
12332+
///
12333+
/// - If the call is allowed in semantically-correct programs, but only if
12334+
/// it's never codegen'ed, creates a deferred diagnostic to be emitted if
12335+
/// and when the caller is codegen'ed, and returns true.
12336+
///
12337+
/// - Otherwise, returns true without emitting any diagnostics.
12338+
///
12339+
/// Adds Callee to DeviceCallGraph if we don't know if its caller will be
12340+
/// codegen'ed yet.
12341+
bool checkSYCLDeviceFunction(SourceLocation Loc, FunctionDecl *Callee);
12342+
12343+
/// Emit diagnostic that can't be emitted with deferred diagnostics mechanism.
12344+
/// At this step we imply that all device functions are marked with
12345+
/// sycl_device attribute.
12346+
void finalizeSYCLDelayedAnalysis();
1233012347
};
1233112348

1233212349
template <typename AttrType>

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4124,6 +4124,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
41244124
} else if (IsSYCL) {
41254125
// Ensure the default version in SYCL mode is 1.2.1
41264126
CmdArgs.push_back("-sycl-std=1.2.1");
4127+
// The user had not pass SYCL version, thus we'll employ no-sycl-strict
4128+
// to allow address-space unqualified pointers in function params/return
4129+
// along with marking the same function with explicit SYCL_EXTERNAL
4130+
CmdArgs.push_back("-Wno-sycl-strict");
41274131
}
41284132

41294133
if (IsOpenMPDevice) {

clang/lib/Sema/Sema.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,7 @@ void Sema::ActOnEndOfTranslationUnitFragment(TUFragmentKind Kind) {
974974
if (SyclIntHeader != nullptr)
975975
SyclIntHeader->emit(getLangOpts().SYCLIntHeader);
976976
MarkDevice();
977+
finalizeSYCLDelayedAnalysis();
977978
}
978979

979980
// Finalize analysis of OpenMP-specific constructs.

clang/lib/Sema/SemaDecl.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18016,6 +18016,12 @@ Decl *Sema::getObjCDeclContext() const {
1801618016
}
1801718017

1801818018
Sema::FunctionEmissionStatus Sema::getEmissionStatus(FunctionDecl *FD) {
18019+
// Due to SYCL functions are template we check if they have appropriate
18020+
// attribute prior to checking if it is a template
18021+
if (LangOpts.SYCLIsDevice &&
18022+
(FD->hasAttr<SYCLDeviceAttr>() || FD->hasAttr<SYCLKernelAttr>()))
18023+
return FunctionEmissionStatus::Emitted;
18024+
1801918025
// Templates are emitted when they're instantiated.
1802018026
if (FD->isDependentContext())
1802118027
return FunctionEmissionStatus::TemplateDiscarded;
@@ -18080,6 +18086,23 @@ Sema::FunctionEmissionStatus Sema::getEmissionStatus(FunctionDecl *FD) {
1808018086
return FunctionEmissionStatus::Emitted;
1808118087
}
1808218088

18089+
if (getLangOpts().SYCLIsDevice) {
18090+
if (!FD->hasAttr<SYCLDeviceAttr>() && !FD->hasAttr<SYCLKernelAttr>())
18091+
return FunctionEmissionStatus::Unknown;
18092+
18093+
// Check whether this function is externally visible -- if so, it's
18094+
// known-emitted.
18095+
//
18096+
// We have to check the GVA linkage of the function's *definition* -- if we
18097+
// only have a declaration, we don't know whether or not the function will
18098+
// be emitted, because (say) the definition could include "inline".
18099+
FunctionDecl *Def = FD->getDefinition();
18100+
18101+
if (Def &&
18102+
!isDiscardableGVALinkage(getASTContext().GetGVALinkageForFunction(Def)))
18103+
return FunctionEmissionStatus::Emitted;
18104+
}
18105+
1808318106
// Otherwise, the function is known-emitted if it's in our set of
1808418107
// known-emitted functions.
1808518108
return (DeviceKnownEmittedFns.count(FD) > 0)

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14684,8 +14684,9 @@ Sema::BuildCXXConstructExpr(SourceLocation ConstructLoc, QualType DeclInitType,
1468414684
MarkFunctionReferenced(ConstructLoc, Constructor);
1468514685
if (getLangOpts().CUDA && !CheckCUDACall(ConstructLoc, Constructor))
1468614686
return ExprError();
14687-
if (getLangOpts().SYCLIsDevice)
14688-
checkSYCLDeviceFunction(ConstructLoc, Constructor);
14687+
if (getLangOpts().SYCLIsDevice &&
14688+
!checkSYCLDeviceFunction(ConstructLoc, Constructor))
14689+
return ExprError();
1468914690

1469014691
return CXXConstructExpr::Create(
1469114692
Context, DeclInitType, ConstructLoc, Constructor, Elidable,

clang/lib/Sema/SemaExpr.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,8 @@ bool Sema::DiagnoseUseOfDecl(NamedDecl *D, ArrayRef<SourceLocation> Locs,
301301
if (getLangOpts().CUDA && !CheckCUDACall(Loc, FD))
302302
return true;
303303

304-
if (getLangOpts().SYCLIsDevice)
305-
checkSYCLDeviceFunction(Loc, FD);
304+
if (getLangOpts().SYCLIsDevice && !checkSYCLDeviceFunction(Loc, FD))
305+
return true;
306306
}
307307

308308
if (auto *MD = dyn_cast<CXXMethodDecl>(D)) {

clang/lib/Sema/SemaSYCL.cpp

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ class MarkDeviceFunction : public RecursiveASTVisitor<MarkDeviceFunction> {
537537
}
538538
return true;
539539
}
540+
540541
Sema &SemaRef;
541542
};
542543

@@ -1410,18 +1411,6 @@ void Sema::MarkDevice(void) {
14101411
// SYCL device specific diagnostics implementation
14111412
// -----------------------------------------------------------------------------
14121413

1413-
// Do we know that we will eventually codegen the given function?
1414-
static bool isKnownEmitted(Sema &S, FunctionDecl *FD) {
1415-
assert(FD && "Given function may not be null.");
1416-
1417-
if (FD->hasAttr<SYCLDeviceAttr>() || FD->hasAttr<SYCLKernelAttr>())
1418-
return true;
1419-
1420-
// Otherwise, the function is known-emitted if it's in our set of
1421-
// known-emitted functions.
1422-
return S.DeviceKnownEmittedFns.count(FD) > 0;
1423-
}
1424-
14251414
Sema::DeviceDiagBuilder Sema::SYCLDiagIfDeviceCode(SourceLocation Loc,
14261415
unsigned DiagID) {
14271416
assert(getLangOpts().SYCLIsDevice &&
@@ -1430,29 +1419,104 @@ Sema::DeviceDiagBuilder Sema::SYCLDiagIfDeviceCode(SourceLocation Loc,
14301419
DeviceDiagBuilder::Kind DiagKind = [this, FD] {
14311420
if (ConstructingOpenCLKernel || !FD)
14321421
return DeviceDiagBuilder::K_Nop;
1433-
if (isKnownEmitted(*this, FD))
1422+
if (getEmissionStatus(FD) == Sema::FunctionEmissionStatus::Emitted)
14341423
return DeviceDiagBuilder::K_ImmediateWithCallStack;
14351424
return DeviceDiagBuilder::K_Deferred;
14361425
}();
14371426
return DeviceDiagBuilder(DiagKind, Loc, DiagID, FD, *this);
14381427
}
14391428

1440-
void Sema::checkSYCLDeviceFunction(SourceLocation Loc, FunctionDecl *Callee) {
1429+
bool Sema::checkSYCLDeviceFunction(SourceLocation Loc, FunctionDecl *Callee) {
1430+
assert(getLangOpts().SYCLIsDevice &&
1431+
"Should only be called during SYCL compilation");
14411432
assert(Callee && "Callee may not be null.");
14421433

14431434
// Errors in unevaluated context don't need to be generated,
14441435
// so we can safely skip them.
1445-
if (isUnevaluatedContext())
1446-
return;
1436+
if (isUnevaluatedContext() || isConstantEvaluated())
1437+
return true;
14471438

14481439
FunctionDecl *Caller = dyn_cast<FunctionDecl>(getCurLexicalContext());
14491440

1441+
if (!Caller)
1442+
return true;
1443+
1444+
bool CallerKnownEmitted =
1445+
getEmissionStatus(Caller) == FunctionEmissionStatus::Emitted;
1446+
14501447
// If the caller is known-emitted, mark the callee as known-emitted.
14511448
// Otherwise, mark the call in our call graph so we can traverse it later.
1452-
if (Caller && isKnownEmitted(*this, Caller))
1453-
markKnownEmitted(*this, Caller, Callee, Loc, isKnownEmitted);
1454-
else if (Caller)
1449+
if (CallerKnownEmitted)
1450+
markKnownEmitted(*this, Caller, Callee, Loc, [](Sema &S, FunctionDecl *FD) {
1451+
return S.getEmissionStatus(FD) == Sema::FunctionEmissionStatus::Emitted;
1452+
});
1453+
else
14551454
DeviceCallGraph[Caller].insert({Callee, Loc});
1455+
1456+
DeviceDiagBuilder::Kind DiagKind = DeviceDiagBuilder::K_Nop;
1457+
1458+
// TODO Set DiagKind to K_Immediate/K_Deferred to emit diagnostics for Callee
1459+
1460+
DeviceDiagBuilder(DiagKind, Loc, diag::err_sycl_restrict, Caller, *this)
1461+
<< Sema::KernelCallUndefinedFunction;
1462+
DeviceDiagBuilder(DiagKind, Callee->getLocation(), diag::note_previous_decl,
1463+
Caller, *this)
1464+
<< Callee;
1465+
1466+
return DiagKind != DeviceDiagBuilder::K_Immediate &&
1467+
DiagKind != DeviceDiagBuilder::K_ImmediateWithCallStack;
1468+
}
1469+
1470+
static void emitCallToUndefinedFnDiag(Sema &SemaRef, const FunctionDecl *Callee,
1471+
const FunctionDecl *Caller,
1472+
const SourceLocation &Loc) {
1473+
// Somehow an unspecialized template appears to be in callgraph or list of
1474+
// device functions. We don't want to emit diagnostic here.
1475+
if (Callee->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate)
1476+
return;
1477+
1478+
// Don't emit diagnostic for functions not called from device code
1479+
if (!Caller->hasAttr<SYCLDeviceAttr>() && !Caller->hasAttr<SYCLKernelAttr>())
1480+
return;
1481+
1482+
bool RedeclHasAttr = false;
1483+
1484+
for (const Decl *Redecl : Callee->redecls()) {
1485+
if (const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(Redecl)) {
1486+
if ((FD->hasAttr<SYCLDeviceAttr>() &&
1487+
!FD->getAttr<SYCLDeviceAttr>()->isImplicit()) ||
1488+
FD->hasAttr<SYCLKernelAttr>()) {
1489+
RedeclHasAttr = true;
1490+
break;
1491+
}
1492+
}
1493+
}
1494+
1495+
// Disallow functions with neither definition nor SYCL_EXTERNAL mark
1496+
bool NotDefinedNoAttr = !Callee->isDefined() && !RedeclHasAttr;
1497+
1498+
if (NotDefinedNoAttr && !Callee->getBuiltinID()) {
1499+
SemaRef.Diag(Loc, diag::err_sycl_restrict)
1500+
<< Sema::KernelCallUndefinedFunction;
1501+
SemaRef.Diag(Callee->getLocation(), diag::note_previous_decl) << Callee;
1502+
SemaRef.Diag(Caller->getLocation(), diag::note_called_by) << Caller;
1503+
}
1504+
}
1505+
1506+
void Sema::finalizeSYCLDelayedAnalysis() {
1507+
assert(getLangOpts().SYCLIsDevice &&
1508+
"Should only be called during SYCL compilation");
1509+
1510+
llvm::DenseSet<const FunctionDecl *> Checked;
1511+
1512+
for (const auto &EmittedWithLoc : DeviceKnownEmittedFns) {
1513+
const FunctionDecl *Caller = EmittedWithLoc.getSecond().FD;
1514+
const SourceLocation &Loc = EmittedWithLoc.getSecond().Loc;
1515+
const FunctionDecl *Callee = EmittedWithLoc.getFirst();
1516+
1517+
if (Checked.insert(Callee).second)
1518+
emitCallToUndefinedFnDiag(*this, Callee, Caller, Loc);
1519+
}
14561520
}
14571521

14581522
// -----------------------------------------------------------------------------

clang/test/CodeGenSYCL/address-space-new.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ struct HasX {
1010

1111
struct Y : SpaceWaster, HasX {};
1212

13-
void bar(HasX &hx);
13+
SYCL_EXTERNAL void bar(HasX &hx);
1414

1515
void baz(Y &y) {
1616
bar(y);

clang/test/CodeGenSYCL/bool-vectors.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@ using bool4 = bool __attribute__((ext_vector_type(4)));
1111
using bool8 = bool __attribute__((ext_vector_type(8)));
1212
using bool16 = bool __attribute__((ext_vector_type(16)));
1313

14-
extern bool1 foo1();
14+
extern SYCL_EXTERNAL bool1 foo1();
1515
// CHECK-DAG: declare spir_func zeroext i1 @[[FOO1:[a-zA-Z0-9_]+]]()
16-
extern bool2 foo2();
16+
extern SYCL_EXTERNAL bool2 foo2();
1717
// CHECK-DAG: declare spir_func <2 x i1> @[[FOO2:[a-zA-Z0-9_]+]]()
18-
extern bool3 foo3();
18+
extern SYCL_EXTERNAL bool3 foo3();
1919
// CHECK-DAG: declare spir_func <3 x i1> @[[FOO3:[a-zA-Z0-9_]+]]()
20-
extern bool4 foo4();
20+
extern SYCL_EXTERNAL bool4 foo4();
2121
// CHECK-DAG: declare spir_func <4 x i1> @[[FOO4:[a-zA-Z0-9_]+]]()
22-
extern bool8 foo8();
22+
extern SYCL_EXTERNAL bool8 foo8();
2323
// CHECK-DAG: declare spir_func <8 x i1> @[[FOO8:[a-zA-Z0-9_]+]]()
24-
extern bool16 foo16();
24+
extern SYCL_EXTERNAL bool16 foo16();
2525
// CHECK-DAG: declare spir_func <16 x i1> @[[FOO16:[a-zA-Z0-9_]+]]()
2626

2727
void bar (bool1 b) {};

clang/test/CodeGenSYCL/fpga_pipes.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
// CHECK: %opencl.pipe_ro_t
44

55
using WPipeTy = __attribute__((pipe("write_only"))) const int;
6-
WPipeTy WPipeCreator();
6+
SYCL_EXTERNAL WPipeTy WPipeCreator();
77

88
using RPipeTy = __attribute__((pipe("read_only"))) const int;
9-
RPipeTy RPipeCreator();
9+
SYCL_EXTERNAL RPipeTy RPipeCreator();
1010

1111
template <typename PipeTy>
1212
void foo(PipeTy Pipe) {}
@@ -24,7 +24,7 @@ template <int N>
2424
constexpr PipeStorageTy
2525
TempStorage __attribute__((io_pipe_id(N))) = {2};
2626

27-
void boo(PipeStorageTy PipeStorage);
27+
SYCL_EXTERNAL void boo(PipeStorageTy PipeStorage);
2828

2929
template <int ID>
3030
struct ethernet_pipe {

clang/test/CodeGenSYCL/unique-stable-name.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// CHECK: @[[LAMBDA_IN_DEP_INT:[^\w]+]] = private unnamed_addr constant [[DEP_INT_SIZE:\[[0-9]+ x i8\]]] c"_ZTSZ28lambda_in_dependent_functionIiEvvEUlvE23->12\00",
99
// CHECK: @[[LAMBDA_IN_DEP_X:[^\w]+]] = private unnamed_addr constant [[DEP_LAMBDA_SIZE:\[[0-9]+ x i8\]]] c"_ZTSZ28lambda_in_dependent_functionIZZ4mainENKUlvE42->5clEvEUlvE46->16EvvEUlvE23->12\00",
1010

11-
extern "C" void printf(const char*);
11+
extern "C" void printf(const char *) {}
1212

1313
template <typename T>
1414
void template_param() {

0 commit comments

Comments
 (0)