Skip to content

[SYCL] Fix issue with half and -fsycl-unnamed-lambda #960

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions clang/lib/AST/ItaniumMangle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2445,6 +2445,67 @@ static bool isTypeSubstitutable(Qualifiers Quals, const Type *Ty,
return true;
}

namespace {
struct DeclContextDesc {
Decl::Kind DeclKind;
StringRef Name;
};
} // namespace

// For Scopes argument, the only supported Decl::Kind values are:
// - Namespace
// - CXXRecord
// - ClassTemplateSpecialization
static bool matchQualifiedTypeName(const QualType &Ty,
ArrayRef<DeclContextDesc> Scopes) {
// The idea: check the declaration context chain starting from the type
// itself. At each step check the context is of expected kind
// (namespace) and name.
const CXXRecordDecl *RecTy = Ty->getAsCXXRecordDecl();

if (!RecTy)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be an assert instead? I'd hate for us to think this works for other things, then it fails.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this file I call this function with TagType, which might represent both Record and Enum. So, I would prefer if instead

return false; // only classes/structs supported
const auto *Ctx = dyn_cast<DeclContext>(RecTy);

for (const auto &Scope : llvm::reverse(Scopes)) {
Decl::Kind DK = Ctx->getDeclKind();
StringRef Name = "";

if (DK != Scope.DeclKind)
return false;

switch (DK) {
case Decl::Kind::ClassTemplateSpecialization:
// ClassTemplateSpecializationDecl inherits from CXXRecordDecl
case Decl::Kind::CXXRecord:
Name = cast<CXXRecordDecl>(Ctx)->getName();
break;
case Decl::Kind::Namespace:
Name = cast<NamespaceDecl>(Ctx)->getName();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens with anonymous namespaces?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, getName() returns a unique string hash, so, it won't match with the requested name and the function will just return false

break;
default:
return false;
}
if (Name != Scope.Name)
return false;
Ctx = Ctx->getParent();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out getEnclosingNamespaceContext. If we can get the chart to tell us what we expect out of everything (and I think all are namespaces?) you can probably just use that until you find the TU.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getEnclosingNamespaceContext doesn't seem to work properly:

For getParent(), I see the following chain of (DeclKind, Name) pairs being analyzed:

33      half
14      half_impl
14      detail
14      sycl
14      cl

While for getEnclosingNamespaceContext() it looks like:

33      half
14      half_impl
14      half_impl

It seems like enclosing namespace context for half_impl is half_impl, which is confusing. Probably I don't fully understand something

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... interesting. My reading of the function doesn't really seem like it SHOULD do that, but it is perhaps an old enough function that it isn't terribly maintained. It just seemed to fit the need :)

Is that the entire chain? or did you 'give up' there. There is an interesting call to 'getPrimaryContext' in that function that seems like it should make sure you don't get duplicates...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually... looking at the logic to that function I think it (or my interpretation of it) is wrong... I think it would return half_impl forever. It seems that it never returns the parent of a namespace, just the primary declcontext for the current one (or the namespace containing a current object).

I looked at some other things that do similar work, so I now think getParent is the only way to do this.

}
return Ctx->isTranslationUnit();
}

static bool isSYCLHostHalfType(const Type *Ty) {
// FIXME: this is not really portable, since the bunch of namespace below
// is not specified by the SYCL standard and highly depends on particular
// implementation
static const std::array<DeclContextDesc, 5> Scopes = {
DeclContextDesc{Decl::Kind::Namespace, "cl"},
DeclContextDesc{Decl::Kind::Namespace, "sycl"},
DeclContextDesc{Decl::Kind::Namespace, "detail"},
DeclContextDesc{Decl::Kind::Namespace, "half_impl"},
DeclContextDesc{Decl::Kind::CXXRecord, "half"}};
return matchQualifiedTypeName(QualType(Ty, 0), Scopes);
}

void CXXNameMangler::mangleType(QualType T) {
// If our type is instantiation-dependent but not dependent, we mangle
// it as it was written in the source, removing any top-level sugar.
Expand Down Expand Up @@ -2504,6 +2565,11 @@ void CXXNameMangler::mangleType(QualType T) {

bool isSubstitutable =
isTypeSubstitutable(quals, ty, Context.getASTContext());
if (Context.isUniqueNameMangler() && isSYCLHostHalfType(ty)) {
// Set isSubstitutable to false for cl::sycl::detail::half_impl::half
// to achieve the same mangling for other components
isSubstitutable = false;
}
if (isSubstitutable && mangleSubstitution(T))
return;

Expand Down Expand Up @@ -2980,6 +3046,11 @@ void CXXNameMangler::mangleType(const RecordType *T) {
mangleType(static_cast<const TagType*>(T));
}
void CXXNameMangler::mangleType(const TagType *T) {
if (Context.isUniqueNameMangler() && isSYCLHostHalfType(T)) {
// Mangle cl::sycl::detail::half_imple::half as _Float16
mangleType(Context.getASTContext().Float16Ty);
return;
}
mangleName(T->getDecl());
}

Expand Down
68 changes: 68 additions & 0 deletions clang/test/CodeGenSYCL/half-with-unnamed-lambda.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// RUN: %clangxx -fsycl-device-only -fsycl-unnamed-lambda -emit-llvm %s -o %t1.bc
// RUN: llvm-dis %t1.bc -o - | FileCheck %s
// RUN: %clangxx -fsycl-device-only -fsycl-unnamed-lambda -emit-llvm %s -DUSE_WRAPPER=1 -o %t2.bc
// RUN: llvm-dis %t2.bc -o - | FileCheck %s

// Mangling of kernel lambda must be the same for both versions of half
// CHECK: __unique_stable_name{{.*}} = private unnamed_addr constant [52 x i8] c"_ZTSN2cl4sycl6bufferINS0_4pairIDF16_NS0_5dummyEEEEE\00"

// Helper function to get string returned by __unique_stable_name in LLVM IR
template <typename T>
void print() {
auto temp = __unique_stable_name(T);
}

// Helper function to get "print" emitted in device code
template<typename T, typename F>
__attribute__((sycl_kernel)) void helper(F f) {
print<T>();
f();
}

// Half wrapper, as it defined in SYCL headers
namespace cl {
namespace sycl {
namespace detail {
namespace half_impl {
class half {
public:
half operator=(int) {return *this;}
};
} // namespace half_impl
} // namespace detail
} // namespace sycl
} // namespace cl

#ifndef USE_WRAPPER
using half = _Float16;
#else
using half = cl::sycl::detail::half_impl::half;
#endif

// A few more fake data types to complicate the mangling
namespace cl {
namespace sycl {
struct dummy {
int a;
};
template<typename T1, typename T2>
struct pair {
T1 a;
T2 b;
};
template <typename T>
class buffer {
public:
T &operator[](int) const { return value; }
mutable T value;
};
} // namespace sycl
} // namespace cl

int main() {
cl::sycl::buffer<cl::sycl::pair<half, cl::sycl::dummy>> B1;

helper<decltype(B1)>([](){});

return 0;
}
43 changes: 43 additions & 0 deletions sycl/test/regression/fp16-with-unnamed-lambda.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// RUN: %clangxx -fsycl -fsycl-unnamed-lambda %s -o %t.out
// RUN: %GPU_RUN_PLACEHOLDER %t.out
#include <CL/sycl.hpp>

#include <iostream>

int main() {
auto AsyncHandler = [](cl::sycl::exception_list EL) {
for (std::exception_ptr const &P : EL) {
try {
std::rethrow_exception(P);
} catch (std::exception const &E) {
std::cerr << "Caught async SYCL exception: " << E.what() << std::endl;
}
}
};

cl::sycl::queue Q(AsyncHandler);

cl::sycl::device D = Q.get_device();
if (!D.has_extension("cl_khr_fp16"))
return 0; // Skip the test if halfs are not supported

cl::sycl::buffer<cl::sycl::cl_half> Buf(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume just half type also will work, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, there is no such thing as half according to the SYCL spec, see KhronosGroup/SYCL-CTS#37

But we have such alias in our implementation and it should also work, because cl::sycl::cl_half is declared as an alias to half

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope. SYCL spec defines half. See Table 6.1. https://www.khronos.org/registry/SYCL/specs/sycl-1.2.1.pdf . There is half defined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see.

However, it says that "all standard C++ fundamental types from Table 6.1", while half is not a standard fundamental data type, see Floating point types

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But half also is presented in this table.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to fix the spec if you think there are some issues. :-)


Q.submit([&](cl::sycl::handler &CGH) {
auto Acc = Buf.get_access<cl::sycl::access::mode::write>(CGH);
CGH.single_task([=]() {
Acc[0] = 1;
});
});

Q.wait_and_throw();

auto Acc = Buf.get_access<cl::sycl::access::mode::read>();
if (1 != Acc[0]) {
std::cerr << "Incorrect result, got: " << Acc[0]
<< ", expected: 1" << std::endl;
return 1;
}

return 0;
}