Skip to content

Commit 4ceba5b

Browse files
[SYCL] Prevent stream buffer leak on constructor exception (#4594)
The `sycl::stream` class is currently throwing a `sycl::invalid_parameter_error` exception in the constructor when the `MaxStatementSize` parameter exceeds the `MAX_STATEMENT_SIZE` limitation. However, this exception is thrown after the underlying `stream_impl` object and the corresponding accessors are initialized. `stream_impl` allocates the buffers for the stream but leaves it to the scheduler to deallocate it. Since the exception is thrown prior to registering the stream with the command-group handler, the allocated buffers will leak if the exception is thrown. These changes prevents the memory leak by making the check prior to the initialization of the `stream_impl`, which in turn avoids the creation of the stream buffers and accessors if the check fails. This also avoids allocating memory that will never be used.
1 parent 01351f1 commit 4ceba5b

File tree

4 files changed

+140
-8
lines changed

4 files changed

+140
-8
lines changed

sycl/source/stream.cpp

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,28 @@ namespace sycl {
1717
static constexpr size_t MAX_STATEMENT_SIZE =
1818
(1 << (CHAR_BIT * detail::FLUSH_BUF_OFFSET_SIZE)) - 1;
1919

20-
stream::stream(size_t BufferSize, size_t MaxStatementSize, handler &CGH)
21-
: impl(std::make_shared<detail::stream_impl>(BufferSize, MaxStatementSize,
22-
CGH)),
23-
GlobalBuf(impl->accessGlobalBuf(CGH)),
24-
GlobalOffset(impl->accessGlobalOffset(CGH)),
25-
// Allocate the flush buffer, which contains space for each work item
26-
GlobalFlushBuf(impl->accessGlobalFlushBuf(CGH)),
27-
FlushBufferSize(MaxStatementSize + detail::FLUSH_BUF_OFFSET_SIZE) {
20+
// Checks the MaxStatementSize argument of the sycl::stream class. This is
21+
// called on MaxStatementSize as it is passed to the constructor of the
22+
// underlying stream_impl to make it throw before the stream buffers are
23+
// allocated, avoiding memory leaks.
24+
static size_t CheckMaxStatementSize(const size_t &MaxStatementSize) {
2825
if (MaxStatementSize > MAX_STATEMENT_SIZE) {
2926
throw sycl::invalid_parameter_error(
3027
"Maximum statement size exceeds limit of " +
3128
std::to_string(MAX_STATEMENT_SIZE) + " bytes.",
3229
PI_INVALID_VALUE);
3330
}
31+
return MaxStatementSize;
32+
}
3433

34+
stream::stream(size_t BufferSize, size_t MaxStatementSize, handler &CGH)
35+
: impl(std::make_shared<detail::stream_impl>(
36+
BufferSize, CheckMaxStatementSize(MaxStatementSize), CGH)),
37+
GlobalBuf(impl->accessGlobalBuf(CGH)),
38+
GlobalOffset(impl->accessGlobalOffset(CGH)),
39+
// Allocate the flush buffer, which contains space for each work item
40+
GlobalFlushBuf(impl->accessGlobalFlushBuf(CGH)),
41+
FlushBufferSize(MaxStatementSize + detail::FLUSH_BUF_OFFSET_SIZE) {
3542
// Save stream implementation in the handler so that stream will be alive
3643
// during kernel execution
3744
CGH.addStream(impl);

sycl/unittests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ add_subdirectory(pi)
1818
add_subdirectory(kernel-and-program)
1919
add_subdirectory(queue)
2020
add_subdirectory(scheduler)
21+
add_subdirectory(stream)
2122
add_subdirectory(SYCL2020)
2223
add_subdirectory(thread_safety)
2324
add_subdirectory(program_manager)

sycl/unittests/stream/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
add_sycl_unittest(StreamTests OBJECT
2+
stream.cpp
3+
)

sycl/unittests/stream/stream.cpp

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//==---------------- stream.cpp --- SYCL stream unit test ------------------==//
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+
9+
#include <CL/sycl.hpp>
10+
11+
#include <helpers/CommonRedefinitions.hpp>
12+
#include <helpers/PiImage.hpp>
13+
#include <helpers/PiMock.hpp>
14+
15+
#include <gtest/gtest.h>
16+
17+
#include <limits>
18+
19+
class TestKernel;
20+
21+
__SYCL_INLINE_NAMESPACE(cl) {
22+
namespace sycl {
23+
namespace detail {
24+
template <> struct KernelInfo<TestKernel> {
25+
static constexpr unsigned getNumParams() { return 0; }
26+
static const kernel_param_desc_t &getParamDesc(int) {
27+
static kernel_param_desc_t Dummy;
28+
return Dummy;
29+
}
30+
static constexpr const char *getName() { return "Stream_TestKernel"; }
31+
static constexpr bool isESIMD() { return false; }
32+
static constexpr bool callsThisItem() { return false; }
33+
static constexpr bool callsAnyThisFreeFunction() { return false; }
34+
};
35+
} // namespace detail
36+
} // namespace sycl
37+
} // __SYCL_INLINE_NAMESPACE(cl)
38+
39+
static sycl::unittest::PiImage generateDefaultImage() {
40+
using namespace sycl::unittest;
41+
42+
PiPropertySet PropSet;
43+
44+
std::vector<unsigned char> Bin{0, 1, 2, 3, 4, 5}; // Random data
45+
46+
PiArray<PiOffloadEntry> Entries = makeEmptyKernels({"Stream_TestKernel"});
47+
48+
PiImage Img{PI_DEVICE_BINARY_TYPE_SPIRV, // Format
49+
__SYCL_PI_DEVICE_BINARY_TARGET_SPIRV64, // DeviceTargetSpec
50+
"", // Compile options
51+
"", // Link options
52+
std::move(Bin),
53+
std::move(Entries),
54+
std::move(PropSet)};
55+
56+
return Img;
57+
}
58+
59+
static sycl::unittest::PiImage Img = generateDefaultImage();
60+
static sycl::unittest::PiImageArray<1> ImgArray{&Img};
61+
62+
size_t GBufferCreateCounter = 0;
63+
64+
static pi_result
65+
redefinedMemBufferCreate(pi_context context, pi_mem_flags flags, size_t size,
66+
void *host_ptr, pi_mem *ret_mem,
67+
const pi_mem_properties *properties = nullptr) {
68+
++GBufferCreateCounter;
69+
*ret_mem = nullptr;
70+
return PI_SUCCESS;
71+
}
72+
73+
TEST(Stream, TestStreamConstructorExceptionNoAllocation) {
74+
sycl::platform Plt{sycl::default_selector()};
75+
if (Plt.is_host()) {
76+
std::cout << "Not run on host - no PI buffers created in that case"
77+
<< std::endl;
78+
return;
79+
}
80+
81+
if (Plt.get_backend() == sycl::backend::cuda) {
82+
std::cout << "Test is not supported on CUDA platform, skipping\n";
83+
return;
84+
}
85+
86+
if (Plt.get_backend() == sycl::backend::hip) {
87+
std::cout << "Test is not supported on HIP platform, skipping\n";
88+
return;
89+
}
90+
91+
sycl::unittest::PiMock Mock{Plt};
92+
setupDefaultMockAPIs(Mock);
93+
Mock.redefine<sycl::detail::PiApiKind::piMemBufferCreate>(
94+
redefinedMemBufferCreate);
95+
96+
const sycl::device Dev = Plt.get_devices()[0];
97+
sycl::queue Queue{Dev};
98+
const sycl::context Ctx = Queue.get_context();
99+
100+
sycl::kernel_bundle KernelBundle =
101+
sycl::get_kernel_bundle<sycl::bundle_state::input>(Ctx, {Dev});
102+
auto ExecBundle = sycl::build(KernelBundle);
103+
104+
Queue.submit([&](sycl::handler &CGH) {
105+
CGH.use_kernel_bundle(ExecBundle);
106+
107+
try {
108+
// Try to create stream with invalid workItemBufferSize parameter.
109+
sycl::stream InvalidStream{256, std::numeric_limits<size_t>::max(), CGH};
110+
FAIL() << "No exception was thrown.";
111+
} catch (const sycl::invalid_parameter_error &) {
112+
// Expected exception
113+
} catch (...) {
114+
FAIL() << "Unexpected exception was thrown.";
115+
}
116+
117+
CGH.single_task<TestKernel>([=]() {});
118+
});
119+
120+
ASSERT_EQ(GBufferCreateCounter, 0u) << "Buffers were unexpectedly created.";
121+
}

0 commit comments

Comments
 (0)