Skip to content

Commit d76b552

Browse files
authored
[SYCL] Add indeterminate constructor to work group memory interface (#16003)
Implement the changes made to the work group memory spec in this PR: #15933. Essentially, the default constructor is removed from the spec and instead a constructor that takes an indeterminate argument is made available in order to emphasize that any operation on such a work group memory object is undefined behavior except for assigning to it another work group memory object. Because the frontend needs special types to have a default constructor, we make it private instead and when its time for the frontend to call it, simply override the access specifier temporarily.
1 parent 2dd9a28 commit d76b552

File tree

5 files changed

+119
-20
lines changed

5 files changed

+119
-20
lines changed

clang/lib/Sema/SemaSYCL.cpp

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3950,13 +3950,26 @@ class SyclKernelBodyCreator : public SyclKernelFieldHandler {
39503950
}
39513951

39523952
// Default inits the type, then calls the init-method in the body.
3953+
// A type may not have a public default constructor as per its spec so
3954+
// typically if this is the case the default constructor will be private and
3955+
// in such cases we must manually override the access specifier from private
3956+
// to public just for the duration of this default initialization.
3957+
// TODO: Revisit this approach once https://github.com/intel/llvm/issues/16061
3958+
// is closed.
39533959
bool handleSpecialType(FieldDecl *FD, QualType Ty) {
3960+
const auto *RecordDecl = Ty->getAsCXXRecordDecl();
3961+
AccessSpecifier DefaultConstructorAccess;
3962+
auto DefaultConstructor =
3963+
std::find_if(RecordDecl->ctor_begin(), RecordDecl->ctor_end(),
3964+
[](auto it) { return it->isDefaultConstructor(); });
3965+
DefaultConstructorAccess = DefaultConstructor->getAccess();
3966+
DefaultConstructor->setAccess(AS_public);
3967+
39543968
addFieldInit(FD, Ty, std::nullopt,
39553969
InitializationKind::CreateDefault(KernelCallerSrcLoc));
3956-
3970+
DefaultConstructor->setAccess(DefaultConstructorAccess);
39573971
addFieldMemberExpr(FD, Ty);
39583972

3959-
const auto *RecordDecl = Ty->getAsCXXRecordDecl();
39603973
createSpecialMethodCall(RecordDecl, getInitMethodName(), BodyStmts);
39613974
CXXMethodDecl *FinalizeMethod =
39623975
getMethodByName(RecordDecl, FinalizeMethodName);
@@ -3970,9 +3983,17 @@ class SyclKernelBodyCreator : public SyclKernelFieldHandler {
39703983
}
39713984

39723985
bool handleSpecialType(const CXXBaseSpecifier &BS, QualType Ty) {
3973-
const auto *RecordDecl = Ty->getAsCXXRecordDecl();
3986+
const auto *BaseRecordDecl = BS.getType()->getAsCXXRecordDecl();
3987+
AccessSpecifier DefaultConstructorAccess;
3988+
auto DefaultConstructor =
3989+
std::find_if(BaseRecordDecl->ctor_begin(), BaseRecordDecl->ctor_end(),
3990+
[](auto it) { return it->isDefaultConstructor(); });
3991+
DefaultConstructorAccess = DefaultConstructor->getAccess();
3992+
DefaultConstructor->setAccess(AS_public);
3993+
39743994
addBaseInit(BS, Ty, InitializationKind::CreateDefault(KernelCallerSrcLoc));
3975-
createSpecialMethodCall(RecordDecl, getInitMethodName(), BodyStmts);
3995+
DefaultConstructor->setAccess(DefaultConstructorAccess);
3996+
createSpecialMethodCall(BaseRecordDecl, getInitMethodName(), BodyStmts);
39763997
return true;
39773998
}
39783999

@@ -4669,16 +4690,21 @@ class SyclKernelIntHeaderCreator : public SyclKernelFieldHandler {
46694690
bool handleSyclSpecialType(const CXXRecordDecl *RD,
46704691
const CXXBaseSpecifier &BC,
46714692
QualType FieldTy) final {
4672-
const auto *AccTy =
4673-
cast<ClassTemplateSpecializationDecl>(FieldTy->getAsRecordDecl());
4674-
assert(AccTy->getTemplateArgs().size() >= 2 &&
4675-
"Incorrect template args for Accessor Type");
4676-
int Dims = static_cast<int>(
4677-
AccTy->getTemplateArgs()[1].getAsIntegral().getExtValue());
4678-
int Info = getAccessTarget(FieldTy, AccTy) | (Dims << 11);
4679-
Header.addParamDesc(SYCLIntegrationHeader::kind_accessor, Info,
4680-
CurOffset +
4681-
offsetOf(RD, BC.getType()->getAsCXXRecordDecl()));
4693+
if (isSyclAccessorType(FieldTy)) {
4694+
const auto *AccTy =
4695+
cast<ClassTemplateSpecializationDecl>(FieldTy->getAsRecordDecl());
4696+
assert(AccTy->getTemplateArgs().size() >= 2 &&
4697+
"Incorrect template args for Accessor Type");
4698+
int Dims = static_cast<int>(
4699+
AccTy->getTemplateArgs()[1].getAsIntegral().getExtValue());
4700+
int Info = getAccessTarget(FieldTy, AccTy) | (Dims << 11);
4701+
Header.addParamDesc(SYCLIntegrationHeader::kind_accessor, Info,
4702+
CurOffset +
4703+
offsetOf(RD, BC.getType()->getAsCXXRecordDecl()));
4704+
} else if (SemaSYCL::isSyclType(FieldTy, SYCLTypeAttr::work_group_memory)) {
4705+
addParam(FieldTy, SYCLIntegrationHeader::kind_work_group_memory,
4706+
offsetOf(RD, BC.getType()->getAsCXXRecordDecl()));
4707+
}
46824708
return true;
46834709
}
46844710

clang/test/SemaSYCL/Inputs/sycl.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,23 @@ class __SYCL_TYPE(multi_ptr) multi_ptr<T, AS, access::decorated::legacy> {
448448
pointer_t m_Pointer;
449449
};
450450

451+
// Dummy implementation of work_group_memory for use in SemaSYCL tests.
452+
template <typename DataT>
453+
class __attribute__((sycl_special_class))
454+
__SYCL_TYPE(work_group_memory) work_group_memory {
455+
456+
// Default constructor for objects later initialized with __init member.
457+
work_group_memory() = default;
458+
459+
public:
460+
work_group_memory(handler &CGH) {}
461+
462+
void __init(__attribute((opencl_local)) DataT *Ptr) { this->Ptr = Ptr; }
463+
void use() const {}
464+
private:
465+
__attribute((opencl_local)) DataT *Ptr;
466+
};
467+
451468
namespace ext {
452469
namespace oneapi {
453470
namespace experimental {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// RUN: %clang_cc1 -fsycl-is-device -internal-isystem %S/Inputs -ast-dump -sycl-std=2020 %s | FileCheck %s
2+
3+
// Check that AST is correctly generated for kernel arguments that inherit from work group memory.
4+
5+
#include "sycl.hpp"
6+
7+
sycl::queue myQueue;
8+
9+
struct WorkGroupMemoryDerived :
10+
sycl::work_group_memory<int> {
11+
};
12+
13+
int main() {
14+
myQueue.submit([&](sycl::handler &h) {
15+
WorkGroupMemoryDerived DerivedObject{ h };
16+
h.parallel_for<class kernel>([=] {
17+
DerivedObject.use();
18+
});
19+
});
20+
return 0;
21+
}
22+
23+
// CHECK: FunctionDecl {{.*}}kernel{{.*}} 'void (__local int *)'
24+
// CHECK-NEXT: ParmVarDecl {{.*}}used _arg__base '__local int *'
25+
// CHECK-NEXT: CompoundStmt {{.*}}
26+
// CHECK-NEXT: DeclStmt {{.*}}
27+
// CHECK-NEXT: VarDecl {{.*}} used __SYCLKernel {{.*}} cinit
28+
// CHECK-NEXT: InitListExpr {{.*}}
29+
// CHECK-NEXT: InitListExpr {{.*}} 'WorkGroupMemoryDerived'
30+
// CHECK-NEXT: CXXConstructExpr {{.*}} 'sycl::work_group_memory<int>' 'void () noexcept'
31+
// CHECK-NEXT: CXXMemberCallExpr {{.*}} 'void'
32+
// CHECK-NEXT: MemberExpr {{.*}} 'void (__local int *)' lvalue .__init {{.*}}
33+
// CHECK-NEXT: MemberExpr {{.*}} 'WorkGroupMemoryDerived' lvalue .DerivedObject
34+
// CHECK-NEXT: DeclRefExpr {{.*}} lvalue Var {{.*}} '__SYCLKernel'
35+
// CHECK-NEXT: ImplicitCastExpr {{.*}} '__local int *' <LValueToRValue>
36+
// CHECK-NEXT: DeclRefExpr {{.*}} '__local int *' lvalue ParmVar {{.*}} '_arg__base' '__local int *'
37+
// CHECK-NEXT: CompoundStmt {{.*}}
38+
// CHECK-NEXT: CXXOperatorCallExpr {{.*}} 'void' '()'
39+
// CHECK-NEXT: ImplicitCastExpr {{.*}} 'auto (*)() const -> void' <FunctionToPointerDecay>
40+
// CHECK-NEXT: DeclRefExpr {{.*}}'auto () const -> void' lvalue CXXMethod {{.*}} 'operator()' 'auto () const -> void'
41+
// CHECK-NEXT: ImplicitCastExpr {{.*}}
42+
// CHECK-NEXT: DeclRefExpr {{.*}}lvalue Var {{.*}} '__SYCLKernel'
43+

sycl/include/sycl/ext/oneapi/experimental/work_group_memory.hpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ class work_group_memory_impl {
3636
} // namespace detail
3737
namespace ext::oneapi::experimental {
3838

39+
struct indeterminate_t {};
40+
inline constexpr indeterminate_t indeterminate;
41+
3942
template <typename DataT, typename PropertyListT = empty_properties_t>
4043
class __SYCL_SPECIAL_CLASS __SYCL_TYPE(work_group_memory) work_group_memory
4144
: sycl::detail::work_group_memory_impl {
@@ -46,8 +49,20 @@ class __SYCL_SPECIAL_CLASS __SYCL_TYPE(work_group_memory) work_group_memory
4649
using decoratedPtr = typename sycl::detail::DecoratedType<
4750
value_type, access::address_space::local_space>::type *;
4851

49-
public:
52+
// Frontend requires special types to have a default constructor in order to
53+
// have a uniform way of initializing an object of special type to then call
54+
// the __init method on it. This is purely an implementation detail and not
55+
// part of the spec.
56+
// TODO: Revisit this once https://github.com/intel/llvm/issues/16061 is
57+
// closed.
5058
work_group_memory() = default;
59+
60+
#ifdef __SYCL_DEVICE_ONLY__
61+
void __init(decoratedPtr ptr) { this->ptr = ptr; }
62+
#endif
63+
64+
public:
65+
work_group_memory(const indeterminate_t &) {};
5166
work_group_memory(const work_group_memory &rhs) = default;
5267
work_group_memory &operator=(const work_group_memory &rhs) = default;
5368
template <typename T = DataT,
@@ -73,9 +88,7 @@ class __SYCL_SPECIAL_CLASS __SYCL_TYPE(work_group_memory) work_group_memory
7388
*ptr = value;
7489
return *this;
7590
}
76-
#ifdef __SYCL_DEVICE_ONLY__
77-
void __init(decoratedPtr ptr) { this->ptr = ptr; }
78-
#endif
91+
7992
private:
8093
decoratedPtr ptr = nullptr;
8194
};

sycl/test-e2e/WorkGroupMemory/swap_test.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ template <typename T> void swap_scalar(T &a, T &b) {
4949
syclexp::work_group_memory<T> temp{cgh};
5050
sycl::nd_range<1> ndr{size, wgsize};
5151
cgh.parallel_for(ndr, [=](sycl::nd_item<1> it) {
52-
syclexp::work_group_memory<T> temp2;
52+
syclexp::work_group_memory<T> temp2{syclexp::indeterminate};
5353
temp2 = temp; // temp and temp2 have the same underlying data
5454
temp = acc_a[0];
5555
acc_a[0] = acc_b[0];
@@ -264,7 +264,7 @@ void swap_array_2d(T (&a)[N][N], T (&b)[N][N], size_t batch_size) {
264264
const auto j = it.get_global_id()[1];
265265
temp[i][j] = acc_a[i][j];
266266
acc_a[i][j] = acc_b[i][j];
267-
syclexp::work_group_memory<T[N][N]> temp2;
267+
syclexp::work_group_memory<T[N][N]> temp2{syclexp::indeterminate};
268268
temp2 = temp;
269269
acc_b[i][j] = temp2[i][j];
270270
});

0 commit comments

Comments
 (0)