Skip to content

Commit cbd8a72

Browse files
author
Ivan Karachun
authored
[SYCL] Don't throw exceptions from destructors (#1378)
SYCL RT can throw exceptions in case of enqueue process failure. Some commands are scheduled while destructor call. So possible exceptions should be handlen by destructor since throwing exceptions out of a destructor is undefined behaviour. Signed-off-by: Ivan Karachun <[email protected]>
1 parent 7bb4c4f commit cbd8a72

File tree

12 files changed

+164
-22
lines changed

12 files changed

+164
-22
lines changed

sycl/include/CL/sycl/detail/buffer_impl.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,12 @@ class buffer_impl final : public SYCLMemObjT {
108108

109109
MemObjType getType() const override { return MemObjType::BUFFER; }
110110

111-
~buffer_impl() { BaseT::updateHostMemory(); }
111+
~buffer_impl() {
112+
try {
113+
BaseT::updateHostMemory();
114+
} catch (...) {
115+
}
116+
}
112117
};
113118

114119
} // namespace detail

sycl/include/CL/sycl/detail/image_impl.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,12 @@ template <int Dimensions> class image_impl final : public SYCLMemObjT {
216216

217217
size_t getSlicePitch() const { return MSlicePitch; }
218218

219-
~image_impl() { BaseT::updateHostMemory(); }
219+
~image_impl() {
220+
try {
221+
BaseT::updateHostMemory();
222+
} catch (...) {
223+
}
224+
}
220225

221226
private:
222227
vector_class<device> getDevices(const ContextImplPtr Context);

sycl/source/detail/accessor_impl.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ namespace sycl {
1515
namespace detail {
1616

1717
AccessorImplHost::~AccessorImplHost() {
18-
if (MBlockedCmd)
19-
detail::Scheduler::getInstance().releaseHostAccessor(this);
18+
try {
19+
if (MBlockedCmd)
20+
detail::Scheduler::getInstance().releaseHostAccessor(this);
21+
} catch (...) {
22+
}
2023
}
2124

2225
void addHostAccessorAndWait(Requirement *Req) {

sycl/source/detail/scheduler/commands.cpp

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,11 @@ void Command::waitForEvents(QueueImplPtr Queue,
228228
}
229229

230230
Command::Command(CommandType Type, QueueImplPtr Queue)
231-
: MQueue(std::move(Queue)), MType(Type), MEnqueued(false) {
231+
: MQueue(std::move(Queue)), MType(Type) {
232232
MEvent.reset(new detail::event_impl(MQueue));
233233
MEvent->setCommand(this);
234234
MEvent->setContextImpl(detail::getSyclObjImpl(MQueue->get_context()));
235+
MEnqueueStatus = EnqueueResultT::SyclEnqueueReady;
235236

236237
#ifdef XPTI_ENABLE_INSTRUMENTATION
237238
if (!xptiTraceEnabled())
@@ -451,11 +452,11 @@ void Command::emitInstrumentation(uint16_t Type, const char *Txt) {
451452

452453
bool Command::enqueue(EnqueueResultT &EnqueueResult, BlockingT Blocking) {
453454
// Exit if already enqueued
454-
if (MEnqueued)
455+
if (MEnqueueStatus == EnqueueResultT::SyclEnqueueSuccess)
455456
return true;
456457

457458
// If the command is blocked from enqueueing
458-
if (MIsBlockable && !MCanEnqueue) {
459+
if (MIsBlockable && MEnqueueStatus == EnqueueResultT::SyclEnqueueBlocked) {
459460
// Exit if enqueue type is not blocking
460461
if (!Blocking) {
461462
EnqueueResult = EnqueueResultT(EnqueueResultT::SyclEnqueueBlocked, this);
@@ -478,7 +479,7 @@ bool Command::enqueue(EnqueueResultT &EnqueueResult, BlockingT Blocking) {
478479
#endif
479480

480481
// Wait if blocking
481-
while (!MCanEnqueue)
482+
while (MEnqueueStatus == EnqueueResultT::SyclEnqueueBlocked)
482483
;
483484
#ifdef XPTI_ENABLE_INSTRUMENTATION
484485
emitInstrumentation(xpti::trace_barrier_end, Info.c_str());
@@ -488,13 +489,22 @@ bool Command::enqueue(EnqueueResultT &EnqueueResult, BlockingT Blocking) {
488489
std::lock_guard<std::mutex> Lock(MEnqueueMtx);
489490

490491
// Exit if the command is already enqueued
491-
if (MEnqueued)
492+
if (MEnqueueStatus == EnqueueResultT::SyclEnqueueSuccess)
492493
return true;
493494

494495
#ifdef XPTI_ENABLE_INSTRUMENTATION
495496
emitInstrumentation(xpti::trace_task_begin, nullptr);
496497
#endif
497498

499+
if (MEnqueueStatus == EnqueueResultT::SyclEnqueueFailed) {
500+
EnqueueResult = EnqueueResultT(EnqueueResultT::SyclEnqueueFailed, this);
501+
return false;
502+
}
503+
504+
// Command status set to "failed" beforehand, so this command
505+
// has already been marked as "failed" if enqueueImp throws an exception.
506+
// This will avoid execution of the same failed command twice.
507+
MEnqueueStatus = EnqueueResultT::SyclEnqueueFailed;
498508
cl_int Res = enqueueImp();
499509

500510
if (CL_SUCCESS != Res)
@@ -503,14 +513,14 @@ bool Command::enqueue(EnqueueResultT &EnqueueResult, BlockingT Blocking) {
503513
else
504514
// Consider the command is successfully enqueued if return code is
505515
// CL_SUCCESS
506-
MEnqueued = true;
516+
MEnqueueStatus = EnqueueResultT::SyclEnqueueSuccess;
507517

508518
// Emit this correlation signal before the task end
509519
emitEnqueuedEventSignal(MEvent->getHandleRef());
510520
#ifdef XPTI_ENABLE_INSTRUMENTATION
511521
emitInstrumentation(xpti::trace_task_end, nullptr);
512522
#endif
513-
return static_cast<bool>(MEnqueued);
523+
return MEnqueueStatus == EnqueueResultT::SyclEnqueueSuccess;
514524
}
515525

516526
void Command::resolveReleaseDependencies(std::set<Command *> &DepList) {

sycl/source/detail/scheduler/commands.hpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ enum BlockingT { NON_BLOCKING = 0, BLOCKING };
4040

4141
// The struct represents the result of command enqueueing
4242
struct EnqueueResultT {
43-
enum ResultT { SyclEnqueueSuccess, SyclEnqueueBlocked, SyclEnqueueFailed };
43+
enum ResultT {
44+
SyclEnqueueReady,
45+
SyclEnqueueSuccess,
46+
SyclEnqueueBlocked,
47+
SyclEnqueueFailed
48+
};
4449
EnqueueResultT(ResultT Result = SyclEnqueueSuccess, Command *Cmd = nullptr,
4550
cl_int ErrCode = CL_SUCCESS)
4651
: MResult(Result), MCmd(Cmd), MErrCode(ErrCode) {}
@@ -110,7 +115,9 @@ class Command {
110115

111116
bool isFinished();
112117

113-
bool isEnqueued() const { return MEnqueued; }
118+
bool isSuccessfullyEnqueued() const {
119+
return MEnqueueStatus == EnqueueResultT::SyclEnqueueSuccess;
120+
}
114121

115122
std::shared_ptr<queue_impl> getQueue() const { return MQueue; }
116123

@@ -170,8 +177,6 @@ class Command {
170177

171178
// The type of the command
172179
CommandType MType;
173-
// Indicates whether the command is enqueued or not
174-
std::atomic<bool> MEnqueued;
175180
// Mutex used to protect enqueueing from race conditions
176181
std::mutex MEnqueueMtx;
177182

@@ -182,13 +187,14 @@ class Command {
182187
std::unordered_set<Command *> MUsers;
183188
// Indicates whether the command can be blocked from enqueueing
184189
bool MIsBlockable = false;
185-
// Indicates whether the command is blocked from enqueueing
186-
std::atomic<bool> MCanEnqueue;
187190
// Counts the number of memory objects this command is a leaf for
188191
unsigned MLeafCounter = 0;
189192

190193
const char *MBlockReason = "Unknown";
191194

195+
// Describes the status of a command
196+
std::atomic<EnqueueResultT::ResultT> MEnqueueStatus;
197+
192198
// All member variable defined here are needed for the SYCL instrumentation
193199
// layer. Do not guard these variables below with XPTI_ENABLE_INSTRUMENTATION
194200
// to ensure we have the same object layout when the macro in the library and

sycl/source/detail/scheduler/graph_builder.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ Command *Scheduler::GraphBuilder::addHostAccessor(Requirement *Req,
426426
UpdateHostAccCmd->addUser(EmptyCmd);
427427

428428
EmptyCmd->MIsBlockable = true;
429-
EmptyCmd->MCanEnqueue = false;
429+
EmptyCmd->MEnqueueStatus = EnqueueResultT::SyclEnqueueBlocked;
430430
EmptyCmd->MBlockReason = "A Buffer is locked by the host accessor";
431431

432432
updateLeaves({UpdateHostAccCmd}, Record, Req->MAccessMode);

sycl/source/detail/scheduler/graph_processor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ void Scheduler::GraphProcessor::waitForEvent(EventImplPtr Event) {
5555
bool Scheduler::GraphProcessor::enqueueCommand(Command *Cmd,
5656
EnqueueResultT &EnqueueResult,
5757
BlockingT Blocking) {
58-
if (!Cmd || Cmd->isEnqueued())
58+
if (!Cmd || Cmd->isSuccessfullyEnqueued())
5959
return true;
6060

6161
// Indicates whether dependency cannot be enqueued

sycl/source/detail/scheduler/scheduler.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ EventImplPtr Scheduler::addHostAccessor(Requirement *Req,
177177
}
178178

179179
void Scheduler::releaseHostAccessor(Requirement *Req) {
180-
Req->MBlockedCmd->MCanEnqueue = true;
180+
Req->MBlockedCmd->MEnqueueStatus = EnqueueResultT::SyclEnqueueReady;
181181
MemObjRecord* Record = Req->MSYCLMemObj->MRecord.get();
182182
auto EnqueueLeaves = [](CircularBuffer<Command *> &Leaves) {
183183
for (Command *Cmd : Leaves) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %clangxx -fsycl -fsycl-targets=%sycl_triple -I %sycl_source_dir %s -o %t.out
2+
// RUN: %CPU_RUN_PLACEHOLDER %t.out
3+
#include <CL/sycl.hpp>
4+
#include <array>
5+
6+
using namespace cl::sycl;
7+
8+
constexpr access::mode sycl_read = access::mode::read;
9+
constexpr access::mode sycl_write = access::mode::write;
10+
11+
constexpr unsigned MAX_WG_SIZE = 4;
12+
constexpr unsigned SIZE = 5;
13+
using ArrayType = std::array<unsigned, SIZE>;
14+
15+
class kernelCompute;
16+
17+
// Return 'true' if an exception was thrown.
18+
bool run_kernel(const unsigned wg_size) {
19+
ArrayType index;
20+
const unsigned N = index.size();
21+
{
22+
buffer<cl_uint, 1> bufferIdx(index.data(), N);
23+
queue deviceQueue;
24+
try {
25+
deviceQueue.submit([&](handler &cgh) {
26+
auto accessorIdx = bufferIdx.get_access<sycl_read>(cgh);
27+
cgh.parallel_for<class kernelCompute>(
28+
nd_range<1>(range<1>(N), range<1>(wg_size)),
29+
[=](nd_item<1> ID) [[cl::reqd_work_group_size(1, 1, MAX_WG_SIZE)]] {
30+
(void)accessorIdx[ID.get_global_id(0)];
31+
});
32+
});
33+
} catch (nd_range_error &err) {
34+
return true;
35+
} catch (...) {
36+
assert(!"Unknown exception was thrown");
37+
}
38+
}
39+
return false;
40+
}
41+
42+
int main() {
43+
bool success_exception = run_kernel(MAX_WG_SIZE);
44+
assert(!success_exception &&
45+
"Unexpected exception was thrown for success call");
46+
bool fail_exception = run_kernel(SIZE);
47+
assert(fail_exception && "No exception was thrown");
48+
49+
return 0;
50+
}

sycl/unittests/scheduler/BlockedCommands.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ class TestScheduler : public detail::Scheduler {
4141
TEST_F(SchedulerTest, BlockedCommands) {
4242
MockCommand MockCmd(detail::getSyclObjImpl(MQueue));
4343

44+
MockCmd.MEnqueueStatus = detail::EnqueueResultT::SyclEnqueueBlocked;
4445
MockCmd.MIsBlockable = true;
45-
MockCmd.MCanEnqueue = false;
4646
MockCmd.MRetVal = CL_DEVICE_PARTITION_EQUALLY;
4747

4848
detail::EnqueueResultT Res;
@@ -52,7 +52,7 @@ TEST_F(SchedulerTest, BlockedCommands) {
5252
ASSERT_EQ(detail::EnqueueResultT::SyclEnqueueBlocked, Res.MResult)
5353
<< "Result of enqueueing blocked command should be BLOCKED\n";
5454

55-
MockCmd.MCanEnqueue = true;
55+
MockCmd.MEnqueueStatus = detail::EnqueueResultT::SyclEnqueueReady;
5656
Res.MResult = detail::EnqueueResultT::SyclEnqueueSuccess;
5757
MockCmd.MRetVal = CL_DEVICE_PARTITION_EQUALLY;
5858

@@ -65,6 +65,7 @@ TEST_F(SchedulerTest, BlockedCommands) {
6565
ASSERT_EQ(&MockCmd, Res.MCmd) << "Expected different failed command.\n";
6666

6767
Res = detail::EnqueueResultT{};
68+
MockCmd.MEnqueueStatus = detail::EnqueueResultT::SyclEnqueueReady;
6869
MockCmd.MRetVal = CL_SUCCESS;
6970
Enqueued = TestScheduler::enqueueCommand(&MockCmd, Res, detail::BLOCKING);
7071
ASSERT_TRUE(Enqueued &&

sycl/unittests/scheduler/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ set(CMAKE_CXX_COMPILER ${clang})
88

99
add_sycl_unittest(SchedulerTests
1010
BlockedCommands.cpp
11+
FailedCommands.cpp
1112
FinishedCmdCleanup.cpp
1213
LeafLimit.cpp
1314
MemObjCommandCleanup.cpp
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//==----------- FailedCommands.cpp ---- Scheduler unit tests ---------------==//
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 "SchedulerTest.hpp"
10+
11+
#include <CL/cl.h>
12+
#include <CL/sycl.hpp>
13+
#include <detail/scheduler/scheduler.hpp>
14+
15+
#include <gtest/gtest.h>
16+
17+
using namespace cl::sycl;
18+
19+
class MockCommand : public detail::Command {
20+
public:
21+
MockCommand(detail::QueueImplPtr Queue)
22+
: Command(detail::Command::ALLOCA, Queue) {}
23+
void printDot(std::ostream &Stream) const override {}
24+
void emitInstrumentationData() override {}
25+
cl_int enqueueImp() override { return CL_SUCCESS; }
26+
};
27+
28+
class MockScheduler : public detail::Scheduler {
29+
public:
30+
static bool enqueueCommand(detail::Command *Cmd,
31+
detail::EnqueueResultT &EnqueueResult,
32+
detail::BlockingT Blocking) {
33+
return GraphProcessor::enqueueCommand(Cmd, EnqueueResult, Blocking);
34+
}
35+
};
36+
37+
TEST_F(SchedulerTest, FailedDependency) {
38+
detail::Requirement MockReq(/*Offset*/ {0, 0, 0}, /*AccessRange*/ {1, 1, 1},
39+
/*MemoryRange*/ {1, 1, 1},
40+
access::mode::read_write, /*SYCLMemObjT*/ nullptr,
41+
/*Dims*/ 1, /*ElementSize*/ 1);
42+
MockCommand MDep(detail::getSyclObjImpl(MQueue));
43+
MockCommand MUser(detail::getSyclObjImpl(MQueue));
44+
MDep.addUser(&MUser);
45+
MUser.addDep(detail::DepDesc{&MDep, &MockReq, nullptr});
46+
MUser.MEnqueueStatus = detail::EnqueueResultT::SyclEnqueueReady;
47+
MDep.MEnqueueStatus = detail::EnqueueResultT::SyclEnqueueFailed;
48+
49+
detail::EnqueueResultT Res;
50+
bool Enqueued =
51+
MockScheduler::enqueueCommand(&MUser, Res, detail::NON_BLOCKING);
52+
53+
ASSERT_FALSE(Enqueued) << "Enqueue process must fail\n";
54+
ASSERT_EQ(Res.MCmd, &MDep) << "Wrong failed command\n";
55+
ASSERT_EQ(Res.MResult, detail::EnqueueResultT::SyclEnqueueFailed)
56+
<< "Enqueue process must fail\n";
57+
ASSERT_EQ(MUser.MEnqueueStatus, detail::EnqueueResultT::SyclEnqueueReady)
58+
<< "MUser shouldn't be marked as failed\n";
59+
ASSERT_EQ(MDep.MEnqueueStatus, detail::EnqueueResultT::SyclEnqueueFailed)
60+
<< "MDep should be marked as failed\n";
61+
}

0 commit comments

Comments
 (0)