Skip to content

Commit 56c9ec4

Browse files
author
sergei
authored
[SYCL] Implementation of fallback assert (#3767)
Signed-off-by: Sergey Kanaev <[email protected]>
1 parent 7d8ef08 commit 56c9ec4

31 files changed

+1075
-50
lines changed

libdevice/atomic.hpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//==-------------- atomic.hpp - support of atomic operations ---------------==//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
#pragma once
9+
10+
#include <cstdint>
11+
12+
#include "device.h"
13+
14+
#ifdef __SPIR__
15+
16+
#define SPIR_GLOBAL __attribute__((opencl_global))
17+
18+
namespace __spv {
19+
struct Scope {
20+
21+
enum Flag : uint32_t {
22+
CrossDevice = 0,
23+
Device = 1,
24+
Workgroup = 2,
25+
Subgroup = 3,
26+
Invocation = 4,
27+
};
28+
29+
constexpr Scope(Flag flag) : flag_value(flag) {}
30+
31+
constexpr operator uint32_t() const { return flag_value; }
32+
33+
Flag flag_value;
34+
};
35+
36+
struct MemorySemanticsMask {
37+
38+
enum Flag : uint32_t {
39+
None = 0x0,
40+
Acquire = 0x2,
41+
Release = 0x4,
42+
AcquireRelease = 0x8,
43+
SequentiallyConsistent = 0x10,
44+
UniformMemory = 0x40,
45+
SubgroupMemory = 0x80,
46+
WorkgroupMemory = 0x100,
47+
CrossWorkgroupMemory = 0x200,
48+
AtomicCounterMemory = 0x400,
49+
ImageMemory = 0x800,
50+
};
51+
52+
constexpr MemorySemanticsMask(Flag flag) : flag_value(flag) {}
53+
54+
constexpr operator uint32_t() const { return flag_value; }
55+
56+
Flag flag_value;
57+
};
58+
} // namespace __spv
59+
60+
extern DEVICE_EXTERNAL int
61+
__spirv_AtomicCompareExchange(int SPIR_GLOBAL *, __spv::Scope::Flag,
62+
__spv::MemorySemanticsMask::Flag,
63+
__spv::MemorySemanticsMask::Flag, int, int);
64+
65+
extern DEVICE_EXTERNAL int __spirv_AtomicLoad(const int SPIR_GLOBAL *,
66+
__spv::Scope::Flag,
67+
__spv::MemorySemanticsMask::Flag);
68+
69+
extern DEVICE_EXTERNAL void
70+
__spirv_AtomicStore(int SPIR_GLOBAL *, __spv::Scope::Flag,
71+
__spv::MemorySemanticsMask::Flag, int);
72+
73+
/// Atomically set the value in *Ptr with Desired if and only if it is Expected
74+
/// Return the value which already was in *Ptr
75+
static inline int atomicCompareAndSet(SPIR_GLOBAL int *Ptr, int Desired,
76+
int Expected) {
77+
return __spirv_AtomicCompareExchange(
78+
Ptr, __spv::Scope::Device,
79+
__spv::MemorySemanticsMask::SequentiallyConsistent,
80+
__spv::MemorySemanticsMask::SequentiallyConsistent, Desired, Expected);
81+
}
82+
83+
static inline int atomicLoad(SPIR_GLOBAL int *Ptr) {
84+
return __spirv_AtomicLoad(Ptr, __spv::Scope::Device,
85+
__spv::MemorySemanticsMask::SequentiallyConsistent);
86+
}
87+
88+
static inline void atomicStore(SPIR_GLOBAL int *Ptr, int V) {
89+
__spirv_AtomicStore(Ptr, __spv::Scope::Device,
90+
__spv::MemorySemanticsMask::SequentiallyConsistent, V);
91+
}
92+
93+
#endif // __SPIR__

libdevice/fallback-cassert.cpp

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,93 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9+
#include "atomic.hpp"
10+
#include "include/assert-happened.hpp"
911
#include "wrapper.h"
1012

1113
#ifdef __SPIR__
12-
static const __attribute__((opencl_constant)) char assert_fmt[] =
13-
"%s:%d: %s: global id: [%lu,%lu,%lu], local id: [%lu,%lu,%lu] "
14-
"Assertion `%s` failed.\n";
14+
15+
#define ASSERT_NONE 0
16+
#define ASSERT_START 1
17+
#define ASSERT_FINISH 2
18+
19+
// definition
20+
SPIR_GLOBAL AssertHappened SPIR_AssertHappenedMem;
21+
22+
DEVICE_EXTERN_C void __devicelib_assert_read(void *_Dst) {
23+
AssertHappened *Dst = (AssertHappened *)_Dst;
24+
int Flag = atomicLoad(&SPIR_AssertHappenedMem.Flag);
25+
26+
if (ASSERT_NONE == Flag) {
27+
Dst->Flag = Flag;
28+
return;
29+
}
30+
31+
if (Flag != ASSERT_FINISH)
32+
while (ASSERT_START == atomicLoad(&SPIR_AssertHappenedMem.Flag))
33+
;
34+
35+
*Dst = SPIR_AssertHappenedMem;
36+
}
1537

1638
DEVICE_EXTERN_C void __devicelib_assert_fail(const char *expr, const char *file,
1739
int32_t line, const char *func,
1840
uint64_t gid0, uint64_t gid1,
1941
uint64_t gid2, uint64_t lid0,
2042
uint64_t lid1, uint64_t lid2) {
21-
// intX_t types are used instead of `int' and `long' because the format string
22-
// is defined in terms of *device* types (OpenCL types): %d matches a 32 bit
23-
// integer, %lu matches a 64 bit unsigned integer. Host `int' and
24-
// `long' types may be different, so we cannot use them.
25-
__spirv_ocl_printf(assert_fmt, file, (int32_t)line,
26-
// WORKAROUND: IGC does not handle this well
27-
// (func) ? func : "<unknown function>",
28-
func, gid0, gid1, gid2, lid0, lid1, lid2, expr);
43+
int Expected = ASSERT_NONE;
44+
int Desired = ASSERT_START;
45+
46+
if (atomicCompareAndSet(&SPIR_AssertHappenedMem.Flag, Desired, Expected) ==
47+
Expected) {
48+
SPIR_AssertHappenedMem.Line = line;
49+
SPIR_AssertHappenedMem.GID0 = gid0;
50+
SPIR_AssertHappenedMem.GID1 = gid1;
51+
SPIR_AssertHappenedMem.GID2 = gid2;
52+
SPIR_AssertHappenedMem.LID0 = lid0;
53+
SPIR_AssertHappenedMem.LID1 = lid1;
54+
SPIR_AssertHappenedMem.LID2 = lid2;
55+
56+
int ExprLength = 0;
57+
int FileLength = 0;
58+
int FuncLength = 0;
59+
60+
if (expr)
61+
for (const char *C = expr; *C != '\0'; ++C, ++ExprLength)
62+
;
63+
if (file)
64+
for (const char *C = file; *C != '\0'; ++C, ++FileLength)
65+
;
66+
if (func)
67+
for (const char *C = func; *C != '\0'; ++C, ++FuncLength)
68+
;
69+
70+
int MaxExprIdx = sizeof(SPIR_AssertHappenedMem.Expr) - 1;
71+
int MaxFileIdx = sizeof(SPIR_AssertHappenedMem.File) - 1;
72+
int MaxFuncIdx = sizeof(SPIR_AssertHappenedMem.Func) - 1;
73+
74+
if (ExprLength < MaxExprIdx)
75+
MaxExprIdx = ExprLength;
76+
if (FileLength < MaxFileIdx)
77+
MaxFileIdx = FileLength;
78+
if (FuncLength < MaxFuncIdx)
79+
MaxFuncIdx = FuncLength;
80+
81+
for (int Idx = 0; Idx < MaxExprIdx; ++Idx)
82+
SPIR_AssertHappenedMem.Expr[Idx] = expr[Idx];
83+
SPIR_AssertHappenedMem.Expr[MaxExprIdx] = '\0';
84+
85+
for (int Idx = 0; Idx < MaxFileIdx; ++Idx)
86+
SPIR_AssertHappenedMem.File[Idx] = file[Idx];
87+
SPIR_AssertHappenedMem.File[MaxFileIdx] = '\0';
88+
89+
for (int Idx = 0; Idx < MaxFuncIdx; ++Idx)
90+
SPIR_AssertHappenedMem.Func[Idx] = func[Idx];
91+
SPIR_AssertHappenedMem.Func[MaxFuncIdx] = '\0';
92+
93+
// Show we've done copying
94+
atomicStore(&SPIR_AssertHappenedMem.Flag, ASSERT_FINISH);
95+
}
2996

3097
// FIXME: call SPIR-V unreachable instead
3198
// volatile int *die = (int *)0x0;

libdevice/include/assert-happened.hpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//==-- assert-happened.hpp - Structure and declaration for assert support --==//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
#pragma once
9+
10+
// Treat this header as system one to workaround frontend's restriction
11+
#pragma clang system_header
12+
13+
#ifdef __SPIR__
14+
15+
// NOTE Layout of this structure should be aligned with the one in
16+
// sycl/include/CL/sycl/detail/assert_happened.hpp
17+
struct AssertHappened {
18+
int Flag = 0;
19+
char Expr[256 + 1] = "";
20+
char File[256 + 1] = "";
21+
char Func[128 + 1] = "";
22+
23+
int32_t Line = 0;
24+
25+
uint64_t GID0 = 0;
26+
uint64_t GID1 = 0;
27+
uint64_t GID2 = 0;
28+
29+
uint64_t LID0 = 0;
30+
uint64_t LID1 = 0;
31+
uint64_t LID2 = 0;
32+
};
33+
34+
#ifndef SPIR_GLOBAL_VAR
35+
#ifdef __SYCL_DEVICE_ONLY__
36+
#define SPIR_GLOBAL_VAR __attribute__((sycl_global_var))
37+
#else
38+
#warning "SPIR_GLOBAL_VAR not defined in host mode. Defining as empty macro."
39+
#define SPIR_GLOBAL_VAR
40+
#endif
41+
#endif
42+
43+
#define __SYCL_GLOBAL__ __attribute__((opencl_global))
44+
45+
// declaration
46+
extern SPIR_GLOBAL_VAR __SYCL_GLOBAL__ AssertHappened SPIR_AssertHappenedMem;
47+
48+
#endif

sycl/cmake/modules/AddSYCLUnitTest.cmake

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ macro(add_sycl_unittest test_dirname link_variant)
2121
else()
2222
add_unittest(SYCLUnitTests ${test_dirname}
2323
$<TARGET_OBJECTS:${sycl_obj_target}> ${ARGN})
24-
target_compile_definitions(${test_dirname} PRIVATE __SYCL_BUILD_SYCL_DLL)
24+
target_compile_definitions(${test_dirname}
25+
PRIVATE __SYCL_BUILD_SYCL_DLL)
2526

2627
get_target_property(SYCL_LINK_LIBS ${sycl_so_target} LINK_LIBRARIES)
2728
endif()

sycl/doc/Assert.md

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,9 @@ The following sequence of events describes how user code gets notified:
149149
2. A host-task is enqueued to check value of assert failure flag.
150150
3. The host task calls abort whenever assert failure flag is set.
151151
152-
DPCPP Runtime will automatically check if assertions are enabled in the kernel
152+
DPCPP Runtime will automatically check if assertions are used in the kernel
153153
being run, and won't enqueue the auxiliary kernels if assertions are not
154-
enabled. So there is no host-side runtime overhead when assertion are not
155-
enabled.
154+
used. So there is no host-side runtime overhead when assertion are not used.
156155
157156
Illustrating this with an example, lets assume the user enqueues three kernels:
158157
- `Kernel #1`, uses assert
@@ -172,18 +171,25 @@ same binary image where fallback `__devicelib_assert_fail` resides.
172171
declaration:</a>
173172
174173
```c++
175-
namespace cl {
176-
namespace sycl {
177-
namespace detail {
178-
struct AssertHappened {
174+
struct __SYCL_AssertHappened {
179175
int Flag = 0;
176+
char Expr[256 + 1] = "";
177+
char File[256 + 1] = "";
178+
char Func[128 + 1] = "";
179+
180+
int32_t Line = 0;
181+
182+
uint64_t GID0 = 0;
183+
uint64_t GID1 = 0;
184+
uint64_t GID2 = 0;
185+
186+
uint64_t LID0 = 0;
187+
uint64_t LID1 = 0;
188+
uint64_t LID2 = 0;
180189
};
181-
}
182-
}
183-
}
184190
185191
#ifdef __SYCL_DEVICE_ONLY__
186-
extern SYCL_GLOBAL_VAR AssertHappened AssertHappenedMem;
192+
extern SYCL_GLOBAL_VAR __SYCL_AssertHappened __SYCL_AssertHappenedMem;
187193
#endif
188194
```
189195

@@ -193,6 +199,28 @@ mutable program-scope variable.
193199
The reference to extern variable is resolved within online-linking against
194200
fallback devicelib.
195201

202+
#### Description of fields
203+
204+
The value stored here denotes if assert happened at all. There are two valid
205+
values at host:
206+
207+
| Value | Meaning |
208+
| ----- | ------- |
209+
| 0 | No assert failure detected |
210+
| 2 | Assert failure detected and reported within this instance of struct |
211+
212+
At device-side, there's another valid value: 1, which means that assert failure
213+
is detected and the structure is filling up at the moment. This value is for
214+
device-side only and should never be reported to host. Otherwise, it means, that
215+
atomic operation malfunctioned.
216+
217+
`Expr`, `File`, `Func`, `Line` are to describe the assert message itself and
218+
contain the expression, file name, function name, line in the file where assert
219+
failure had happened respectively.
220+
221+
`GID*` and `LID*` fields describe the global and local ID respectively of a
222+
work-item in which assert had failed.
223+
196224
### Online-linking fallback `__devicelib_assert_fail`
197225

198226
Online linking against fallback implementation of `__devicelib_assert_fail` is

sycl/doc/PreprocessorMacros.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ This file describes macros that have effect on SYCL compiler and run-time.
3333

3434
Disables all deprecation warnings in SYCL runtime headers, including SYCL 1.2.1 deprecations.
3535

36+
- **SYCL_DISABLE_FALLBACK_ASSERT**
37+
38+
Defining this macro eliminates some overhead that is associated with
39+
submitting kernels that call `assert()`. When this macro is defined, the logic
40+
for detecting assertion failures in kernels is disabled, so a failed assert
41+
will not cause a message to be printed and will not cause the program to
42+
abort. However, this macro only affects kernels that are submitted to devices
43+
that do **not** have native support for `assert()` because devices with native
44+
support do not impose any extra overhead. One can check to see if a device has
45+
native support for `assert()` via `aspect::ext_oneapi_native_assert`.
46+
3647
## Version macros
3748

3849
- `__LIBSYCL_MAJOR_VERSION` is set to SYCL runtime library major version.

sycl/doc/extensions/C-CXX-StandardLibrary/DeviceLibExtensions.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@ cl_intel_devicelib_cassert
1414
__generic const char *func,
1515
size_t gid0, size_t gid1, size_t gid2,
1616
size_t lid0, size_t lid1, size_t lid2);
17+
1718
Semantic:
1819
the function is called when an assertion expression `expr` is false,
1920
and it indicates that a program does not execute as expected.
2021
The function should print a message containing the information
2122
provided in the arguments. In addition to that, the function is free
2223
to terminate the current kernel invocation.
2324

25+
Fallback implementation of the function raises a flag to be read later by `__devicelib_assert_read`.
26+
The flag remains raised until the program finishes.
27+
2428
Arguments:
2529

2630
- `expr` is a string representation of the assertion condition
@@ -33,6 +37,16 @@ Example of a message:
3337
.. code:
3438
foo.cpp:42: void foo(int): global id: [0,0,0], local id: [0,0,0] Assertion `buf[wiID] == 0 && "Invalid value"` failed.
3539
40+
.. code:
41+
int __devicelib_assert_read();
42+
43+
Semantic:
44+
the function is called to read assert failure flag raised by
45+
`__devicelib_assert_fail`.
46+
The function is only used in fallback implementation.
47+
Invoking `__devicelib_assert_read` after a kernel doesn't imply the kernel has
48+
assertion failed.
49+
3650
See also: assert_extension_.
3751
.. _assert_extension: ../Assert/SYCL_ONEAPI_ASSERT.asciidoc)
3852

sycl/include/CL/sycl/aspects.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ enum class aspect {
4646
atomic64 = 28,
4747
ext_intel_device_info_uuid = 29,
4848
ext_oneapi_srgb = 30,
49+
ext_oneapi_native_assert = 31,
4950
};
5051

5152
} // namespace sycl

0 commit comments

Comments
 (0)