Skip to content

Commit 0039ee0

Browse files
Andrew Savonichevromanovvlad
authored andcommitted
[SYCL] Implement device libraries: C assert (#922)
Device libraries provide a way to support functions from standard C and C++ system headers in SYCL device code. Runtime library requirements (functions that are normally defined in libc, libm or libstdc++) are provided in a SPIR-V library (libsycl-fallback.spv), which is linked at JIT (or AOT) time. If a particular device supports a library OpenCL extension (e.g. cl_intel_devicelib_assert), then SPIR-V implementation is not linked and the device compiler has to provide definitions for the corresponding functions. Signed-off-by: Andrew Savonichev <[email protected]>
1 parent 80d17b2 commit 0039ee0

File tree

18 files changed

+907
-18
lines changed

18 files changed

+907
-18
lines changed

sycl/doc/SYCLEnvironmentVariables.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ subject to change. Do not rely on these variables in production code.
1818
| SYCL_DUMP_IMAGES | Any(*) | Dump device image binaries to file. Control has no effect if SYCL_USE_KERNEL_SPV is set. |
1919
| SYCL_PRINT_EXECUTION_GRAPH | Described [below](#sycl_print_execution_graph-options) | Print execution graph to DOT text file. |
2020
| SYCL_THROW_ON_BLOCK | Any(*) | Throw an exception on attempt to wait for a blocked command. |
21+
| SYCL_DEVICELIB_INHIBIT_NATIVE | String of device library extensions (separated by a whitespace) | Do not rely on device native support for devicelib extensions listed in this option. |
2122

2223
`(*) Note: Any means this environment variable is effective when set to any non-null value.`
2324

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
C and C++ Standard libraries support
2+
===================================
3+
4+
This extension enables a set of functions from the C and C++ standard
5+
libraries, and allows to use them in SYCL device code.
6+
7+
Function declarations are taken from the standard headers (e.g. from
8+
<assert.h> or <cassert>), and the corresponding header has to be
9+
explicitly included in user code.
10+
11+
Implementation requires a special device library to be linked with a
12+
SYCL program. The library should match the C or C++ standard library
13+
used to compile the program:
14+
15+
For example, on Linux with GNU glibc:
16+
.. code:
17+
clang++ -fsycl -c main.cpp -o main.o
18+
clang++ -fsycl main.o $(SYCL_INSTALL)/lib/libsycl-glibc.o -o a.out
19+
20+
or, in case of Windows:
21+
.. code:
22+
clang++ -fsycl -c main.cpp -o main.obj
23+
clang++ -fsycl main.obj %SYCL_INSTALL%/lib/libsycl-msvc.o -o a.exe
24+
25+
List of supported functions from C standard library:
26+
- assert macro (from <assert.h> or <cassert>)
27+
28+
NOTE: only the GNU glibc and Microsoft C libraries are currently
29+
supported.
30+
31+
Example of usage
32+
================
33+
34+
.. code: c++
35+
#include <assert.h>
36+
#include <CL/sycl.hpp>
37+
38+
template <typename T, size_t N>
39+
void simple_vadd(const std::array<T, N>& VA, const std::array<T, N>& VB,
40+
std::array<T, N>& VC) {
41+
// ...
42+
cl::sycl::range<1> numOfItems{N};
43+
cl::sycl::buffer<T, 1> bufferA(VA.data(), numOfItems);
44+
cl::sycl::buffer<T, 1> bufferB(VB.data(), numOfItems);
45+
cl::sycl::buffer<T, 1> bufferC(VC.data(), numOfItems);
46+
47+
deviceQueue.submit([&](cl::sycl::handler& cgh) {
48+
auto accessorA = bufferA.template get_access<sycl_read>(cgh);
49+
auto accessorB = bufferB.template get_access<sycl_read>(cgh);
50+
auto accessorC = bufferC.template get_access<sycl_write>(cgh);
51+
52+
cgh.parallel_for<class SimpleVadd<T>>(numOfItems,
53+
[=](cl::sycl::id<1> wiID) {
54+
accessorC[wiID] = accessorA[wiID] + accessorB[wiID];
55+
assert(accessorC[wiID] > 0 && "Invalid value");
56+
});
57+
});
58+
deviceQueue.wait_and_throw();
59+
}
60+
61+
Frontend
62+
========
63+
64+
Once the system header is included, the corresponding functions can be
65+
used in SYCL device code. This results in a handful of unresolved
66+
functions in LLVM IR after clang:
67+
68+
.. code:
69+
; Function Attrs: noreturn nounwind
70+
declare dso_local spir_func void @__assert_fail(i8 addrspace(4)*, i8 addrspace(4)*, i32, i8 addrspace(4)*)
71+
72+
[...]
73+
cond.false:
74+
call spir_func void @__assert_fail([...])
75+
unreachable
76+
77+
The C and C++ specifications do not define names and signatures of the
78+
functions from libc implementation that are used for a particular
79+
function. For example, the `assert` macro:
80+
81+
- in Glibc and musl libraries it expands to `__assert_fail`
82+
- in MSVC library it expands to `_wassert`
83+
- in newlib library it expands to `__assert_func`
84+
85+
This makes it difficult to handle all possible cases in device
86+
compilers. In order to facilitate porting to new platforms, and to
87+
avoid imposing a lot of boilerplate code in *every* device compiler,
88+
wrapper libraries are provided with the SYCL compiler that "lower"
89+
libc implementation-specific functions into a stable set of functions,
90+
that can be later handled by a device compiler.
91+
92+
.. code:
93+
clang++ -fsycl -c main.cpp -o main.o
94+
clang++ -fsycl main.o $(SYCL_INSTALL)/lib/libsycl-glibc.o -o a.out
95+
96+
This `libsycl-glibc.o` is one of these wrapper libraries: it provides
97+
definitions for glibc specific library function, and these definitions
98+
call the corresponding functions from `__devicelib_*` set of
99+
functions.
100+
101+
For example, `__assert_fail` from IR above gets transformed into:
102+
.. code:
103+
; Function Attrs: noreturn nounwind
104+
declare dso_local spir_func void @__devicelib_assert_fail(i8 addrspace(4)*, i8 addrspace(4)*, i32, i8 addrspace(4)*)
105+
106+
; Function Attrs: noreturn nounwind
107+
define dso_local spir_func void @__assert_fail(i8 addrspace(4)*, i8 addrspace(4)*, i32, i8 addrspace(4)*) {
108+
call spir_func void @__devicelib_assert_fail([...])
109+
}
110+
111+
[...]
112+
cond.false:
113+
call spir_func void @__assert_fail([...])
114+
unreachable
115+
116+
A single wrapper object provides function wrappers for *all* supported
117+
library functions. Every supported C library implementation (MSVC or
118+
glibc) has its own wrapper library object:
119+
120+
- libsycl-glibc.o
121+
- libsycl-msvc.o
122+
123+
SPIR-V
124+
======
125+
126+
Standard library functions are represented as external (import)
127+
functions in SPIR-V:
128+
129+
.. code:
130+
8 Decorate 67 LinkageAttributes "__devicelib_assert_fail" Import
131+
...
132+
2 Label 846
133+
8 FunctionCall 63 864 67 855 857 863 859
134+
1 Unreachable
135+
136+
Device compiler
137+
===============
138+
139+
Device compiler is free to implement these `__devicelib_*` functions.
140+
In order to indicate support for a particular set of functions,
141+
underlying runtime have to support the corresponding OpenCL (PI)
142+
extension. See ``DeviceLibExtensions.rst`` for a list of supported
143+
functions and corresponding extensions.
144+
145+
Fallback implementation
146+
=======================
147+
148+
If a device compiler does not indicate "native" support for a
149+
particular function, a fallback library is linked at JIT time by the
150+
SYCL Runtime. This library is distributed with the SYCL Runtime and
151+
resides in the same directory as the `libsycl.so` or `sycl.dll`.
152+
153+
A fallback library is implemented as a device-agnostic SPIR-V program,
154+
and it is supposed to work for any device that supports SPIR-V.
155+
156+
Every set of functions is implemented in a separate fallback
157+
library. For example, a fallback for `cl_intel_devicelib_cassert`
158+
extension is provided as `libsycl-fallback-cassert.spv`
159+
160+
NOTE that AOT compilation is not yet supported. Driver will have to
161+
check for extension support and link the corresponding SPIR-V fallback
162+
implementation, but this is not implemented yet.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
Device library extensions
2+
===================================
3+
4+
Device compiler that indicates support for a particular extension is
5+
supposed to support *all* the corresponding functions.
6+
7+
cl_intel_devicelib_cassert
8+
==========================
9+
10+
.. code:
11+
void __devicelib_assert_fail(__generic const char *expr,
12+
__generic const char *file,
13+
int32_t line,
14+
__generic const char *func,
15+
size_t gid0, size_t gid1, size_t gid2,
16+
size_t lid0, size_t lid1, size_t lid2);
17+
Semantic:
18+
the function is called when an assertion expression `expr` is false,
19+
and it indicates that a program does not execute as expected.
20+
The function should print a message containing the information
21+
provided in the arguments. In addition to that, the function is free
22+
to terminate the current kernel invocation.
23+
24+
Arguments:
25+
26+
- `expr` is a string representation of the assertion condition
27+
- `file` and `line` are the source code location of the assertion
28+
- `func` (optional, may be NULL) name of the function containing the assertion
29+
- `gidX` current work-item global id
30+
- `lidX` current work-item local id
31+
32+
Example of a message:
33+
.. code:
34+
foo.cpp:42: void foo(int): global id: [0,0,0], local id: [0,0,0] Assertion `buf[wiID] == 0 && "Invalid value"` failed.

sycl/include/CL/sycl/detail/context_impl.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <CL/sycl/detail/os_util.hpp>
1313
#include <CL/sycl/detail/pi.hpp>
1414
#include <CL/sycl/detail/platform_impl.hpp>
15+
#include <CL/sycl/detail/program_manager/program_manager.hpp>
1516
#include <CL/sycl/detail/usm_dispatch.hpp>
1617
#include <CL/sycl/exception_list.hpp>
1718
#include <CL/sycl/info/info_desc.hpp>
@@ -103,6 +104,12 @@ class context_impl {
103104
/// @return an instance of raw plug-in context handle.
104105
const RT::PiContext &getHandleRef() const;
105106

107+
/// Unlike `get_info<info::context::devices>', this function returns a
108+
/// reference.
109+
const vector_class<device> &getDevices() const {
110+
return MDevices;
111+
}
112+
106113
/// Gets cached programs.
107114
///
108115
/// @return a map of cached programs.
@@ -122,6 +129,23 @@ class context_impl {
122129
///
123130
/// @return a pointer to USM dispatcher.
124131
std::shared_ptr<usm::USMDispatcher> getUSMDispatch() const;
132+
133+
/// In contrast to user programs, which are compiled from user code, library
134+
/// programs come from the SYCL runtime. They are identified by the
135+
/// corresponding extension:
136+
///
137+
/// cl_intel_devicelib_assert -> #<pi_program with assert functions>
138+
/// cl_intel_devicelib_complex -> #<pi_program with complex functions>
139+
/// etc.
140+
///
141+
/// See `doc/extensions/C-CXX-StandardLibrary/DeviceLibExtensions.rst' for
142+
/// more details.
143+
///
144+
/// @returns a map with device library programs.
145+
std::map<DeviceLibExt, RT::PiProgram> &getCachedLibPrograms() {
146+
return MCachedLibPrograms;
147+
}
148+
125149
private:
126150
async_handler MAsyncHandler;
127151
vector_class<device> MDevices;
@@ -132,6 +156,7 @@ class context_impl {
132156
std::map<KernelSetId, RT::PiProgram> MCachedPrograms;
133157
std::map<RT::PiProgram, std::map<string_class, RT::PiKernel>> MCachedKernels;
134158
std::shared_ptr<usm::USMDispatcher> MUSMDispatch;
159+
std::map<DeviceLibExt, RT::PiProgram> MCachedLibPrograms;
135160
};
136161

137162
} // namespace detail

sycl/include/CL/sycl/detail/pi.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ typedef enum {
9191
PI_DEVICE_INFO_PARTITION_TYPE = CL_DEVICE_PARTITION_TYPE,
9292
PI_DEVICE_INFO_NAME = CL_DEVICE_NAME,
9393
PI_DEVICE_VERSION = CL_DEVICE_VERSION,
94-
PI_DEVICE_MAX_WORK_GROUP_SIZE = CL_DEVICE_MAX_WORK_GROUP_SIZE
94+
PI_DEVICE_MAX_WORK_GROUP_SIZE = CL_DEVICE_MAX_WORK_GROUP_SIZE,
95+
PI_DEVICE_INFO_EXTENSIONS = CL_DEVICE_EXTENSIONS
9596
} _pi_device_info;
9697

9798
// TODO: populate

sycl/include/CL/sycl/detail/program_manager/program_manager.hpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ using DeviceImage = pi_device_binary_struct;
4141
// be attempted to de-allocate.
4242
struct ImageDeleter;
4343

44+
enum DeviceLibExt {
45+
cl_intel_devicelib_assert = 0
46+
};
47+
4448
// Provides single loading and building OpenCL programs with unique contexts
4549
// that is necessary for no interoperability cases with lambda.
4650
class ProgramManager {
@@ -70,8 +74,13 @@ class ProgramManager {
7074

7175
DeviceImage &getDeviceImage(OSModuleHandle M, KernelSetId KSId,
7276
const context &Context);
73-
void build(RT::PiProgram Program, const string_class &Options,
74-
std::vector<RT::PiDevice> Devices);
77+
using ProgramPtr = unique_ptr_class<remove_pointer_t<RT::PiProgram>,
78+
decltype(&::piProgramRelease)>;
79+
ProgramPtr build(ProgramPtr Program, RT::PiContext Context,
80+
const string_class &Options,
81+
const std::vector<RT::PiDevice> &Devices,
82+
std::map<DeviceLibExt, RT::PiProgram> &CachedLibPrograms,
83+
bool LinkDeviceLibs = false);
7584
/// Provides a new kernel set id for grouping kernel names together
7685
KernelSetId getNextKernelSetId() const;
7786
/// Returns the kernel set associated with the kernel, handles some special

sycl/include/CL/sycl/device.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ class device {
157157

158158
template <class Obj>
159159
friend decltype(Obj::impl) detail::getSyclObjImpl(const Obj &SyclObject);
160+
161+
template <class T>
162+
friend
163+
typename std::add_pointer<typename decltype(T::impl)::element_type>::type
164+
detail::getRawSyclObjImpl(const T &SyclObject);
165+
160166
template <class T>
161167
friend T detail::createSyclObjFromImpl(decltype(T::impl) ImplObj);
162168
};

sycl/source/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,4 @@ install(TARGETS ${SYCL_RT_LIBS}
143143
ARCHIVE DESTINATION "lib" COMPONENT sycl
144144
LIBRARY DESTINATION "lib" COMPONENT sycl
145145
RUNTIME DESTINATION "bin" COMPONENT sycl)
146+
add_subdirectory(detail/devicelib)

sycl/source/detail/context_impl.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ context_impl::~context_impl() {
9090
PI_CALL(piKernelRelease)(KernIt.second);
9191
PI_CALL(piProgramRelease)(ToBeDeleted);
9292
}
93+
for (auto LibProg : MCachedLibPrograms) {
94+
assert(LibProg.second && "Null program must not be kept in the cache");
95+
PI_CALL(piProgramRelease)(LibProg.second);
96+
}
9397
}
9498

9599
const async_handler &context_impl::get_async_handler() const {
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Place device libraries near the libsycl.so library in a build
2+
# directory
3+
if (WIN32)
4+
set(binary_dir "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
5+
else()
6+
set(binary_dir "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
7+
endif()
8+
9+
set(clang $<TARGET_FILE:clang>)
10+
11+
set(compile_opts
12+
# suppress an error about SYCL_EXTERNAL
13+
-Wno-sycl-strict
14+
# for CL/__spirv/spirv_vars.hpp
15+
-I${sycl_inc_dir})
16+
17+
if (WIN32)
18+
set(devicelib-obj-file ${binary_dir}/libsycl-msvc.o)
19+
add_custom_command(OUTPUT ${devicelib-obj-file}
20+
COMMAND ${clang} -fsycl -c
21+
${compile_opts}
22+
${CMAKE_CURRENT_SOURCE_DIR}/msvc_wrapper.cpp
23+
-o ${devicelib-obj-file}
24+
MAIN_DEPENDENCY msvc_wrapper.cpp
25+
DEPENDS wrapper.h clang
26+
VERBATIM)
27+
else()
28+
set(devicelib-obj-file ${binary_dir}/libsycl-glibc.o)
29+
add_custom_command(OUTPUT ${devicelib-obj-file}
30+
COMMAND ${clang} -fsycl -c
31+
${compile_opts}
32+
${CMAKE_CURRENT_SOURCE_DIR}/glibc_wrapper.cpp
33+
-o ${devicelib-obj-file}
34+
MAIN_DEPENDENCY glibc_wrapper.cpp
35+
DEPENDS wrapper.h clang
36+
VERBATIM)
37+
endif()
38+
39+
add_custom_command(OUTPUT ${binary_dir}/libsycl-fallback-cassert.spv
40+
COMMAND ${clang} -S -fsycl-device-only -fno-sycl-use-bitcode
41+
${compile_opts}
42+
${CMAKE_CURRENT_SOURCE_DIR}/fallback-cassert.cpp
43+
-o ${binary_dir}/libsycl-fallback-cassert.spv
44+
MAIN_DEPENDENCY fallback-cassert.cpp
45+
DEPENDS wrapper.h clang llvm-spirv
46+
VERBATIM)
47+
48+
add_custom_target(devicelib-obj DEPENDS ${devicelib-obj-file})
49+
add_custom_target(devicelib-spv DEPENDS ${binary_dir}/libsycl-fallback-cassert.spv)
50+
add_dependencies(sycl devicelib-obj devicelib-spv)
51+
if (MSVC)
52+
add_dependencies(sycld devicelib-obj devicelib-spv)
53+
endif()
54+
55+
# Place device libraries near the libsycl.so library in an install
56+
# directory as well
57+
if (WIN32)
58+
set(install_dest bin)
59+
else()
60+
set(install_dest lib)
61+
endif()
62+
63+
install(FILES ${devicelib-obj-file}
64+
${binary_dir}/libsycl-fallback-cassert.spv
65+
DESTINATION ${install_dest}
66+
COMPONENT sycl)

0 commit comments

Comments
 (0)