[llvm] [libsycl] extend sycl::event API with get_wait_list, wait_and_throw and default ctor (PR #205860)

Kseniya Tikhomirova via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 29 05:37:26 PDT 2026


https://github.com/KseniyaTikhomirova updated https://github.com/llvm/llvm-project/pull/205860

>From 240751af904e96940a6350c151df6071c07bff8a Mon Sep 17 00:00:00 2001
From: "Tikhomirova, Kseniya" <kseniya.tikhomirova at intel.com>
Date: Thu, 18 Jun 2026 07:53:51 -0700
Subject: [PATCH] [libsycl] extend sycl::event API

Signed-off-by: Tikhomirova, Kseniya <kseniya.tikhomirova at intel.com>
---
 libsycl/docs/index.rst                        |   6 +-
 .../__impl/detail/default_async_handler.hpp   |  55 -------
 libsycl/include/sycl/__impl/event.hpp         |  30 ++++
 libsycl/include/sycl/__impl/exception.hpp     |  13 ++
 libsycl/include/sycl/__impl/info/event.hpp    |  53 ++++++
 libsycl/include/sycl/__impl/queue.hpp         |  46 ++++--
 libsycl/src/detail/event_impl.cpp             |  12 ++
 libsycl/src/detail/event_impl.hpp             |  36 ++++-
 libsycl/src/detail/global_objects.cpp         |  38 +++++
 libsycl/src/detail/global_objects.hpp         |  32 ++++
 libsycl/src/detail/queue_impl.cpp             |  52 +++---
 libsycl/src/detail/queue_impl.hpp             |  17 +-
 libsycl/src/detail/spinlock.hpp               |  47 ++++++
 libsycl/src/event.cpp                         |  29 ++++
 libsycl/src/exception_list.cpp                |  23 ++-
 libsycl/src/queue.cpp                         |   4 +
 libsycl/unittests/CMakeLists.txt              |   1 +
 libsycl/unittests/common/device_images.hpp    |   6 +-
 .../common/scoped_binary_registration.hpp     |  64 ++++++++
 libsycl/unittests/common/unittests_helper.hpp |   1 -
 .../device_selector/get_device_preference.cpp |  23 +--
 libsycl/unittests/event/CMakeLists.txt        |   4 +
 libsycl/unittests/event/async_handler.cpp     | 152 ++++++++++++++++++
 libsycl/unittests/event/event.cpp             | 139 ++++++++++++++++
 libsycl/unittests/mock/helpers.cpp            |   7 +
 libsycl/unittests/mock/helpers.hpp            |   1 +
 libsycl/unittests/mock/mock.cpp               |   4 +
 .../register_and_unregister.cpp               |  14 +-
 .../unittests/queue/sycl_kernel_launch.cpp    |  32 +---
 29 files changed, 781 insertions(+), 160 deletions(-)
 delete mode 100644 libsycl/include/sycl/__impl/detail/default_async_handler.hpp
 create mode 100644 libsycl/include/sycl/__impl/info/event.hpp
 create mode 100644 libsycl/src/detail/spinlock.hpp
 create mode 100644 libsycl/unittests/common/scoped_binary_registration.hpp
 create mode 100644 libsycl/unittests/event/CMakeLists.txt
 create mode 100644 libsycl/unittests/event/async_handler.cpp
 create mode 100644 libsycl/unittests/event/event.cpp

diff --git a/libsycl/docs/index.rst b/libsycl/docs/index.rst
index a5d657f3e6404..21120ead9fa15 100644
--- a/libsycl/docs/index.rst
+++ b/libsycl/docs/index.rst
@@ -125,7 +125,11 @@ TODO for added SYCL classes
   * forward templated funcs to alignment methods (rewrite current impl)
   * handle sub devices once they are implemented (blocked by liboffload support)
 
-* ``event``: get_wait_list, get_info, get_profiling_info, wait_and_throw & default ctor are not implemented
+* ``event``:
+
+  * get_info, get_profiling_info (no liboffload support) are not implemented
+  * get_wait_list should be aligned with the results of this discussion: https://github.com/KhronosGroup/SYCL-Docs/issues/1017
+
 * ``range``, ``id`` - __SYCL_DISABLE_ID_TO_INT_CONV__ and __SYCL_ASSUME_ID_RANGE optimizations are not implemented
 * general opens:
 
diff --git a/libsycl/include/sycl/__impl/detail/default_async_handler.hpp b/libsycl/include/sycl/__impl/detail/default_async_handler.hpp
deleted file mode 100644
index 807c8a597f074..0000000000000
--- a/libsycl/include/sycl/__impl/detail/default_async_handler.hpp
+++ /dev/null
@@ -1,55 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-///
-/// \file
-/// This file contains the definition of an implementation-defined default
-/// async_handler, which is invoked when an asynchronous error occurs in a queue
-/// or context that has no user-supplied asynchronous error handler object (see
-/// SYCL 2020 4.13.1.2).
-///
-//===----------------------------------------------------------------------===//
-
-#ifndef _LIBSYCL___IMPL_DETAIL_DEFAULT_ASYNC_HANDLER_HPP
-#define _LIBSYCL___IMPL_DETAIL_DEFAULT_ASYNC_HANDLER_HPP
-
-#include <sycl/__impl/exception.hpp>
-
-#include <iostream>
-
-_LIBSYCL_BEGIN_NAMESPACE_SYCL
-
-namespace detail {
-
-// SYCL 2020 4.13.1.2. Behavior without an async handler.
-// If an asynchronous error occurs in a queue or context that has no
-// user-supplied asynchronous error handler object async_handler, then an
-// implementation-defined default async_handler is called to handle the error in
-// the same situations that a user-supplied async_handler would be. The default
-// async_handler must in some way report all errors passed to it, when possible,
-// and must then invoke std::terminate or equivalent.
-inline void defaultAsyncHandler(exception_list ExceptionList) {
-  std::cerr
-      << "Implementation-defined default async_handler caught exceptions:";
-  for (auto &Exception : ExceptionList) {
-    try {
-      if (Exception) {
-        std::rethrow_exception(Exception);
-      }
-    } catch (const std::exception &E) {
-      std::cerr << "\n\t" << E.what();
-    }
-  }
-  std::cerr << std::endl;
-  std::terminate();
-}
-
-} // namespace detail
-
-_LIBSYCL_END_NAMESPACE_SYCL
-
-#endif // _LIBSYCL___IMPL_DETAIL_DEFAULT_ASYNC_HANDLER_HPP
diff --git a/libsycl/include/sycl/__impl/event.hpp b/libsycl/include/sycl/__impl/event.hpp
index 141b804621f22..7006ffed0fbb0 100644
--- a/libsycl/include/sycl/__impl/event.hpp
+++ b/libsycl/include/sycl/__impl/event.hpp
@@ -20,6 +20,7 @@
 #include <sycl/__impl/detail/config.hpp>
 #include <sycl/__impl/detail/obj_utils.hpp>
 #include <sycl/__impl/info/desc_base.hpp>
+#include <sycl/__impl/info/event.hpp>
 
 #include <memory>
 #include <vector>
@@ -32,11 +33,19 @@ namespace detail {
 class EventImpl;
 template <typename T>
 using is_event_info_desc_t = typename is_info_desc<T, event>::return_type;
+template <typename T>
+using is_event_profiling_info_desc_t =
+    typename is_info_desc<T, event_profiling_tag>::return_type;
 } // namespace detail
 
 /// SYCL 2020 4.6.6. Event class.
 class _LIBSYCL_EXPORT event {
 public:
+  /// Constructs an event that is immediately ready.
+  /// The event has no dependencies and no associated commands. Waiting on this
+  /// event will return immediately.
+  event();
+
   event(const event &rhs) = default;
   event(event &&rhs) = default;
   event &operator=(const event &rhs) = default;
@@ -54,6 +63,10 @@ class _LIBSYCL_EXPORT event {
   /// \return the backend associated with this event.
   backend get_backend() const noexcept;
 
+  /// \return the list of events that this event waits for in the dependence
+  /// graph.
+  std::vector<event> get_wait_list();
+
   /// Blocks until all commands associated with this event and any dependent
   /// events have completed.
   void wait();
@@ -61,6 +74,16 @@ class _LIBSYCL_EXPORT event {
   /// Behaves as if calling event::wait on each event in eventList.
   static void wait(const std::vector<event> &eventList);
 
+  /// Blocks until all commands associated with this event and any dependent
+  /// events have completed. Passes at least all unconsumed asynchronous errors
+  /// held by queues (or their associated contexts) which were used to enqueue
+  /// commands associated with this event and any dependent events, to the
+  /// appropriate async_handler.
+  void wait_and_throw();
+
+  /// Behaves as if calling event::wait_and_throw on each event in eventList.
+  static void wait_and_throw(const std::vector<event> &eventList);
+
   /// Queries this SYCL event for information.
   ///
   /// \return depends on the information being requested.
@@ -73,6 +96,13 @@ class _LIBSYCL_EXPORT event {
   template <typename Param>
   typename Param::return_type get_backend_info() const;
 
+  /// Queries this SYCL event for profiling data.
+  ///
+  /// \return depends on the information being queried.
+  template <typename Param>
+  typename detail::is_event_profiling_info_desc_t<Param>
+  get_profiling_info() const;
+
 private:
   event(std::shared_ptr<detail::EventImpl> Impl) : impl(Impl) {}
   std::shared_ptr<detail::EventImpl> impl;
diff --git a/libsycl/include/sycl/__impl/exception.hpp b/libsycl/include/sycl/__impl/exception.hpp
index add00ebccaa49..bcc535cb0cf60 100644
--- a/libsycl/include/sycl/__impl/exception.hpp
+++ b/libsycl/include/sycl/__impl/exception.hpp
@@ -26,6 +26,11 @@
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
+class exception_list;
+namespace detail {
+void addAsyncException(exception_list &, const std::exception_ptr &);
+}
+
 // int is used as the underlying type for consistency with std::error_code.
 enum class errc : int {
   success = 0,
@@ -142,8 +147,16 @@ class _LIBSYCL_EXPORT exception_list {
 
 private:
   std::vector<std::exception_ptr> MList;
+
+  friend void detail::addAsyncException(exception_list &, const_reference);
 };
 
+namespace detail {
+// Default implementation of async_handler used by queue and context when no
+// user-defined async_handler is specified.
+_LIBSYCL_EXPORT void defaultAsyncHandler(exception_list Exceptions);
+} // namespace detail
+
 _LIBSYCL_END_NAMESPACE_SYCL
 
 namespace std {
diff --git a/libsycl/include/sycl/__impl/info/event.hpp b/libsycl/include/sycl/__impl/info/event.hpp
new file mode 100644
index 0000000000000..d57bf5ec7d099
--- /dev/null
+++ b/libsycl/include/sycl/__impl/info/event.hpp
@@ -0,0 +1,53 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains SYCL 2020 event and event profiling info descriptors.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBSYCL___IMPL_INFO_EVENT_HPP
+#define _LIBSYCL___IMPL_INFO_EVENT_HPP
+
+#include <sycl/__impl/detail/config.hpp>
+#include <sycl/__impl/info/desc_base.hpp>
+
+#include <cstdint>
+
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+
+class event;
+
+namespace detail {
+/// Sentinel type used as the DescOf tag for event profiling info descriptors,
+/// so they are distinct from regular event info descriptors.
+struct event_profiling_tag {};
+} // namespace detail
+
+namespace info {
+namespace event_profiling {
+struct command_submit
+    : detail::info_desc_tag<command_submit, detail::event_profiling_tag> {
+  using return_type = std::uint64_t;
+};
+
+struct command_start
+    : detail::info_desc_tag<command_start, detail::event_profiling_tag> {
+  using return_type = std::uint64_t;
+};
+
+struct command_end
+    : detail::info_desc_tag<command_end, detail::event_profiling_tag> {
+  using return_type = std::uint64_t;
+};
+} // namespace event_profiling
+} // namespace info
+
+_LIBSYCL_END_NAMESPACE_SYCL
+
+#endif // _LIBSYCL___IMPL_INFO_EVENT_HPP
diff --git a/libsycl/include/sycl/__impl/queue.hpp b/libsycl/include/sycl/__impl/queue.hpp
index d05592e5fa3fd..5b92aff49bb9e 100644
--- a/libsycl/include/sycl/__impl/queue.hpp
+++ b/libsycl/include/sycl/__impl/queue.hpp
@@ -21,11 +21,11 @@
 #include <sycl/__impl/property_list.hpp>
 
 #include <sycl/__impl/detail/config.hpp>
-#include <sycl/__impl/detail/default_async_handler.hpp>
 #include <sycl/__impl/detail/get_device_kernel_info.hpp>
 #include <sycl/__impl/detail/kernel_arg_helpers.hpp>
 #include <sycl/__impl/detail/obj_utils.hpp>
 #include <sycl/__impl/detail/unified_range_view.hpp>
+#include <sycl/__impl/exception.hpp>
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
@@ -166,6 +166,17 @@ class _LIBSYCL_EXPORT queue {
   /// exceptions.
   void wait();
 
+  /// Blocks the calling thread until all commands previously submitted to this
+  /// queue have completed. Synchronous errors are reported through SYCL
+  /// exceptions. At least all unconsumed asynchronous errors held by this queue
+  /// are passed to the appropriate async_handler.
+  void wait_and_throw();
+
+  /// Checks to see if any unconsumed asynchronous errors have been produced by
+  /// the queue and if so reports them by passing them to the async_handler
+  /// associated with the queue.
+  void throw_asynchronous();
+
   /// Defines and invokes a SYCL kernel function as a lambda expression or a
   /// named function object type.
   ///
@@ -173,7 +184,8 @@ class _LIBSYCL_EXPORT queue {
   /// \return an event that represents the status of the submitted kernel.
   template <typename KernelName = detail::AutoName, typename KernelType>
   event single_task(const KernelType &kernelFunc) {
-    return single_task<KernelName, KernelType>({}, kernelFunc);
+    return single_task<KernelName, KernelType>(std::vector<event>{},
+                                               kernelFunc);
   }
 
   /// Defines and invokes a SYCL kernel function as a lambda expression or a
@@ -184,7 +196,8 @@ class _LIBSYCL_EXPORT queue {
   /// \return an event that represents the status of the submitted kernel.
   template <typename KernelName = detail::AutoName, typename KernelType>
   event single_task(event depEvent, const KernelType &kernelFunc) {
-    return single_task<KernelName, KernelType>({depEvent}, kernelFunc);
+    return single_task<KernelName, KernelType>(std::vector<event>{depEvent},
+                                               kernelFunc);
   }
 
   /// Defines and invokes a SYCL kernel function as a lambda expression or a
@@ -217,8 +230,8 @@ class _LIBSYCL_EXPORT queue {
   // TODO: Rest will represent reduction types once it is supported.
   template <typename KernelName = detail::AutoName, typename... Rest>
   event parallel_for(range<1> numWorkItems, Rest &&...rest) {
-    return parallel_for<KernelName>(numWorkItems, {},
-                                    std::forward<Rest>(rest)...);
+    return parallel_for<KernelName, Rest...>(numWorkItems, std::vector<event>{},
+                                             std::forward<Rest>(rest)...);
   }
 
   /// Defines and invokes a SYCL kernel function as a lambda expression or a
@@ -229,8 +242,8 @@ class _LIBSYCL_EXPORT queue {
   // TODO: Rest will represent reduction types once it is supported.
   template <typename KernelName = detail::AutoName, typename... Rest>
   event parallel_for(range<2> numWorkItems, Rest &&...rest) {
-    return parallel_for<KernelName>(numWorkItems, {},
-                                    std::forward<Rest>(rest)...);
+    return parallel_for<KernelName, Rest...>(numWorkItems, std::vector<event>{},
+                                             std::forward<Rest>(rest)...);
   }
 
   /// Defines and invokes a SYCL kernel function as a lambda expression or a
@@ -241,8 +254,8 @@ class _LIBSYCL_EXPORT queue {
   // TODO: Rest will represent reduction types once it is supported.
   template <typename KernelName = detail::AutoName, typename... Rest>
   event parallel_for(range<3> numWorkItems, Rest &&...rest) {
-    return parallel_for<KernelName>(numWorkItems, {},
-                                    std::forward<Rest>(rest)...);
+    return parallel_for<KernelName, Rest...>(numWorkItems, std::vector<event>{},
+                                             std::forward<Rest>(rest)...);
   }
 
   /// Defines and invokes a SYCL kernel function as a lambda expression or a
@@ -255,8 +268,9 @@ class _LIBSYCL_EXPORT queue {
   // TODO: Rest will represent reduction types once it is supported.
   template <typename KernelName = detail::AutoName, typename... Rest>
   event parallel_for(range<1> numWorkItems, event depEvent, Rest &&...rest) {
-    return parallel_for<KernelName>(numWorkItems, {depEvent},
-                                    std::forward<Rest>(rest)...);
+    return parallel_for<KernelName, Rest...>(numWorkItems,
+                                             std::vector<event>{depEvent},
+                                             std::forward<Rest>(rest)...);
   }
 
   /// Defines and invokes a SYCL kernel function as a lambda expression or a
@@ -269,8 +283,9 @@ class _LIBSYCL_EXPORT queue {
   // TODO: Rest will represent reduction types once it is supported.
   template <typename KernelName = detail::AutoName, typename... Rest>
   event parallel_for(range<2> numWorkItems, event depEvent, Rest &&...rest) {
-    return parallel_for<KernelName>(numWorkItems, {depEvent},
-                                    std::forward<Rest>(rest)...);
+    return parallel_for<KernelName, Rest...>(numWorkItems,
+                                             std::vector<event>{depEvent},
+                                             std::forward<Rest>(rest)...);
   }
 
   /// Defines and invokes a SYCL kernel function as a lambda expression or a
@@ -283,8 +298,9 @@ class _LIBSYCL_EXPORT queue {
   // TODO: Rest will represent reduction types once it is supported.
   template <typename KernelName = detail::AutoName, typename... Rest>
   event parallel_for(range<3> numWorkItems, event depEvent, Rest &&...rest) {
-    return parallel_for<KernelName>(numWorkItems, {depEvent},
-                                    std::forward<Rest>(rest)...);
+    return parallel_for<KernelName, Rest...>(numWorkItems,
+                                             std::vector<event>{depEvent},
+                                             std::forward<Rest>(rest)...);
   }
 
   /// Defines and invokes a SYCL kernel function as a lambda expression or a
diff --git a/libsycl/src/detail/event_impl.cpp b/libsycl/src/detail/event_impl.cpp
index f4cee5675359b..11d920a7536ab 100644
--- a/libsycl/src/detail/event_impl.cpp
+++ b/libsycl/src/detail/event_impl.cpp
@@ -6,13 +6,20 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include <detail/device_impl.hpp>
 #include <detail/event_impl.hpp>
+#include <detail/global_objects.hpp>
 #include <detail/platform_impl.hpp>
+#include <detail/queue_impl.hpp>
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
 namespace detail {
 
+EventImpl::EventImpl(PrivateTag)
+    : MPlatform(detail::getSyclObjImpl(device(default_selector_v))
+                    ->getPlatformImpl()) {}
+
 EventImpl::~EventImpl() {
   if (MOffloadEvent)
     std::ignore = olDestroyEvent(MOffloadEvent);
@@ -32,5 +39,10 @@ void EventImpl::wait() {
   callAndThrow(olSyncEvent, MOffloadEvent);
 }
 
+void EventImpl::waitAndThrow() {
+  wait();
+  flushAsyncExceptions();
+}
+
 } // namespace detail
 _LIBSYCL_END_NAMESPACE_SYCL
diff --git a/libsycl/src/detail/event_impl.hpp b/libsycl/src/detail/event_impl.hpp
index 235bdb83f0686..3a2ca7316faf1 100644
--- a/libsycl/src/detail/event_impl.hpp
+++ b/libsycl/src/detail/event_impl.hpp
@@ -21,6 +21,7 @@
 #include <OffloadAPI.h>
 
 #include <memory>
+#include <vector>
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 namespace detail {
@@ -42,9 +43,25 @@ class EventImpl {
   EventImpl(ol_event_handle_t Event, PlatformImpl &Platform, PrivateTag)
       : MOffloadEvent(Event), MPlatform(Platform) {}
 
+  /// Constructs a default, immediately ready event.
+  /// The event is constructed as though it were created from a
+  /// default-constructed queue. Therefore, its backend is the same as the
+  /// backend of the device selected by default_selector_v.
+  /// \throw sycl::exception with errc::runtime if no default device is
+  /// available.
+  EventImpl(PrivateTag);
+
   static std::shared_ptr<EventImpl>
-  createEventWithHandle(ol_event_handle_t Event, PlatformImpl &Platform) {
-    return std::make_shared<EventImpl>(Event, Platform, PrivateTag{});
+  createEventWithHandle(ol_event_handle_t Event, PlatformImpl &Platform,
+                        std::vector<std::shared_ptr<EventImpl>> &&WaitList) {
+    auto E = std::make_shared<EventImpl>(Event, Platform, PrivateTag{});
+    E->MWaitList =
+        std::forward<std::vector<std::shared_ptr<EventImpl>>>(WaitList);
+    return E;
+  }
+
+  static std::shared_ptr<EventImpl> createDefaultEvent() {
+    return std::make_shared<EventImpl>(PrivateTag{});
   }
 
   /// Releases the handle to the corresponding liboffload event.
@@ -62,9 +79,24 @@ class EventImpl {
   /// \return the platform implementation object this event belongs to.
   const PlatformImpl &getPlatformImpl() const { return MPlatform; }
 
+  /// Blocks until all commands associated with this event and any dependent
+  /// events have completed. Passes at least all unconsumed asynchronous errors
+  /// held by queues (or their associated contexts) which were used to enqueue
+  /// commands associated with this event and any dependent events, to the
+  /// appropriate async_handler.
+  void waitAndThrow();
+
+  /// \return the list of event implementation objects that this event waits for
+  /// in the dependence graph.
+  const std::vector<std::shared_ptr<EventImpl>> &getWaitList() const {
+    return MWaitList;
+  }
+
 private:
   ol_event_handle_t MOffloadEvent{};
   PlatformImpl &MPlatform;
+
+  std::vector<std::shared_ptr<EventImpl>> MWaitList;
 };
 
 } // namespace detail
diff --git a/libsycl/src/detail/global_objects.cpp b/libsycl/src/detail/global_objects.cpp
index 095912ffcd171..68199567c2706 100644
--- a/libsycl/src/detail/global_objects.cpp
+++ b/libsycl/src/detail/global_objects.cpp
@@ -9,6 +9,7 @@
 #include <detail/global_objects.hpp>
 #include <detail/platform_impl.hpp>
 #include <detail/program_manager.hpp>
+#include <detail/queue_impl.hpp>
 
 #ifdef _WIN32
 #  include <windows.h>
@@ -54,5 +55,42 @@ std::vector<PlatformImplUPtr> &getPlatformCache() {
   return PlatformCache;
 }
 
+InstanceWithLock<AsyncExceptionsContainer> &getAsyncExceptionList() {
+  static InstanceWithLock<AsyncExceptionsContainer> AsyncExceptionList;
+  return AsyncExceptionList;
+}
+
+void reportAsyncException(const std::shared_ptr<QueueImpl> &QueuePtr,
+                          const std::exception_ptr &ExceptionPtr) {
+  auto &[AsyncExceptions, AsyncExceptionsMutex] = getAsyncExceptionList();
+  std::lock_guard<SpinLock> Lock(AsyncExceptionsMutex);
+  addAsyncException(AsyncExceptions[QueuePtr], ExceptionPtr);
+}
+
+void flushAsyncExceptions() {
+  auto &[AsyncExceptions, AsyncExceptionsMutex] = getAsyncExceptionList();
+  AsyncExceptionsContainer AsyncExceptionsSwap;
+  {
+    std::lock_guard<SpinLock> Lock(AsyncExceptionsMutex);
+    std::swap(AsyncExceptions, AsyncExceptionsSwap);
+  }
+
+  for (auto &[EntryKey, ExceptionList] : AsyncExceptionsSwap) {
+    exception_list Exceptions = std::move(ExceptionList);
+
+    if (Exceptions.size() == 0)
+      continue;
+
+    if (std::shared_ptr<QueueImpl> Queue = EntryKey.lock();
+        Queue && Queue->getAsyncHandler()) {
+      Queue->getAsyncHandler()(std::move(Exceptions));
+      continue;
+    }
+
+    // If the queue is dead, use the default handler.
+    defaultAsyncHandler(std::move(Exceptions));
+  }
+}
+
 } // namespace detail
 _LIBSYCL_END_NAMESPACE_SYCL
diff --git a/libsycl/src/detail/global_objects.hpp b/libsycl/src/detail/global_objects.hpp
index d10b352941a8f..f9b12a9ffe2a5 100644
--- a/libsycl/src/detail/global_objects.hpp
+++ b/libsycl/src/detail/global_objects.hpp
@@ -15,16 +15,23 @@
 #define _LIBSYCL_GLOBAL_OBJECTS
 
 #include <detail/offload/offload_topology.hpp>
+#include <detail/spinlock.hpp>
 #include <sycl/__impl/detail/config.hpp>
+#include <sycl/__impl/exception.hpp>
 
+#include <map>
 #include <memory>
 #include <mutex>
+#include <utility>
 #include <vector>
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
 namespace detail {
 class PlatformImpl;
+class QueueImpl;
+
+template <typename T> using InstanceWithLock = std::pair<T, SpinLock>;
 
 /// Returns offload topologies (one per backend) discovered from liboffload.
 ///
@@ -46,6 +53,31 @@ std::vector<std::unique_ptr<PlatformImpl>> &getPlatformCache();
 // the SYCL shared library is first being unloaded.
 void registerStaticVarShutdownHandler();
 
+// TODO: extend with context
+using AsyncExceptionKey = std::weak_ptr<QueueImpl>;
+
+struct AsyncExceptionKeyOwnerLess {
+  bool operator()(const AsyncExceptionKey &LHS,
+                  const AsyncExceptionKey &RHS) const noexcept {
+    return std::owner_less<std::weak_ptr<QueueImpl>>{}(LHS, RHS);
+  }
+};
+
+using AsyncExceptionsContainer =
+    std::map<AsyncExceptionKey, exception_list, AsyncExceptionKeyOwnerLess>;
+
+/// \return pair of container with unreported exceptions and an unlocked
+/// SpinLock.
+InstanceWithLock<AsyncExceptionsContainer> &getAsyncExceptionList();
+
+/// Adds an exception to the list of unreported asynchronous exceptions.
+void reportAsyncException(const std::shared_ptr<QueueImpl> &QueuePtr,
+                          const std::exception_ptr &ExceptionPtr);
+
+/// Reports all unreported asynchronous exceptions to available async_handler
+/// and clears the list.
+void flushAsyncExceptions();
+
 } // namespace detail
 _LIBSYCL_END_NAMESPACE_SYCL
 
diff --git a/libsycl/src/detail/queue_impl.cpp b/libsycl/src/detail/queue_impl.cpp
index beba3082f06e2..19adc1b83ebf3 100644
--- a/libsycl/src/detail/queue_impl.cpp
+++ b/libsycl/src/detail/queue_impl.cpp
@@ -10,6 +10,7 @@
 
 #include <detail/device_impl.hpp>
 #include <detail/event_impl.hpp>
+#include <detail/global_objects.hpp>
 #include <detail/program_manager.hpp>
 
 #include <algorithm>
@@ -66,6 +67,13 @@ backend QueueImpl::getBackend() const noexcept { return MDevice.getBackend(); }
 
 void QueueImpl::wait() { callAndThrow(olSyncQueue, MOffloadQueue); }
 
+void QueueImpl::waitAndThrow() {
+  wait();
+  throwAsynchronous();
+}
+
+void QueueImpl::throwAsynchronous() { flushAsyncExceptions(); }
+
 static bool checkEventsPlatformMatch(std::vector<EventImplPtr> &Events,
                                      const PlatformImpl &QueuePlatform) {
   // liboffload limitation to olWaitEvents. We can't do any extra handling for
@@ -86,18 +94,14 @@ void QueueImpl::setKernelParameters(std::vector<EventImplPtr> &&Events,
         "libsycl doesn't support cross-context/platform event dependencies "
         "now.");
 
-  // TODO: this conversion and storing of only offload events is possible only
-  // while we don't have host tasks (or features based on host tasks, like
-  // streams). With them - it is very likely we should copy EventImplPtr
-  // (shared_ptr) and keep it here. Although it may differ if host tasks will be
-  // implemented on offload level (no data now).
-  assert(MCurrentSubmitInfo.DepEvents.empty() &&
-         "Kernel submission must clean up dependencies.");
-  MCurrentSubmitInfo.DepEvents.reserve(Events.size());
-  for (auto &Event : Events) {
-    assert(Event && "Event impl object can't be nullptr");
-    MCurrentSubmitInfo.DepEvents.push_back(Event->getHandle());
-  }
+  // Clean up previous kernel submit data to prepare structures for submission.
+  // It is done in the beginning of new submission to ensure that if previous
+  // submit throws we still can submit new kernel properly.
+  MCurrentSubmitInfo.DepEvents.clear();
+  MCurrentSubmitInfo.Range = {};
+
+  MCurrentSubmitInfo.DepEvents =
+      std::forward<std::vector<EventImplPtr>>(Events);
   setKernelLaunchArgs(Range, MCurrentSubmitInfo.Range);
 }
 
@@ -115,9 +119,20 @@ void QueueImpl::submitKernelImpl(DeviceKernelInfo &KernelInfo, void *ArgData,
   // must be disabled for in-order queues. Once host tasks are added - cross
   // context dependencies should be enabled and checked as well.
   if (!MCurrentSubmitInfo.DepEvents.empty()) {
-    callAndThrow(olWaitEvents, MOffloadQueue,
-                 MCurrentSubmitInfo.DepEvents.data(),
-                 MCurrentSubmitInfo.DepEvents.size());
+    std::vector<ol_event_handle_t> DepHandles;
+    DepHandles.reserve(MCurrentSubmitInfo.DepEvents.size());
+    for (const auto &Event : MCurrentSubmitInfo.DepEvents) {
+      // NULL handle stands for only 1 case now - default constructed event that
+      // is immediately ready. Ignore it.
+      // TODO: host_task implementation will require to handle NULL handles
+      // differently.
+      if (Event->getHandle())
+        DepHandles.push_back(Event->getHandle());
+    }
+    if (!DepHandles.empty()) {
+      callAndThrow(olWaitEvents, MOffloadQueue, DepHandles.data(),
+                   DepHandles.size());
+    }
   }
 
   assert(ArgData && "At least one argument must exist");
@@ -128,10 +143,6 @@ void QueueImpl::submitKernelImpl(DeviceKernelInfo &KernelInfo, void *ArgData,
   auto Result =
       olLaunchKernel(MOffloadQueue, MDevice.getOLHandle(), Kernel,
                      &MCurrentSubmitInfo.Range, NULL, 1, ArgPtrs, ArgSizes);
-  // Clean up current kernel submit data to prepare structures for next
-  // submission.
-  MCurrentSubmitInfo.DepEvents.clear();
-  MCurrentSubmitInfo.Range = {};
   if (isFailed(Result))
     throw sycl::exception(sycl::make_error_code(sycl::errc::runtime),
                           std::string("Kernel submission (") +
@@ -143,7 +154,8 @@ void QueueImpl::submitKernelImpl(DeviceKernelInfo &KernelInfo, void *ArgData,
   callAndThrow(olCreateEvent, MOffloadQueue, Flags, &NewEvent);
 
   MCurrentSubmitInfo.LastEvent =
-      EventImpl::createEventWithHandle(NewEvent, MDevice.getPlatformImpl());
+      EventImpl::createEventWithHandle(NewEvent, MDevice.getPlatformImpl(),
+                                       std::move(MCurrentSubmitInfo.DepEvents));
 }
 
 } // namespace detail
diff --git a/libsycl/src/detail/queue_impl.hpp b/libsycl/src/detail/queue_impl.hpp
index ac5b303e95dc6..d98788f7fc8e0 100644
--- a/libsycl/src/detail/queue_impl.hpp
+++ b/libsycl/src/detail/queue_impl.hpp
@@ -21,6 +21,7 @@
 #include <OffloadAPI.h>
 
 #include <memory>
+#include <vector>
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 namespace detail {
@@ -71,6 +72,13 @@ class QueueImpl : public std::enable_shared_from_this<QueueImpl> {
   /// Waits for completion of all commands submitted to this queue.
   void wait();
 
+  /// Waits for completion of all commands submitted to this queue and flushes
+  /// unconsumed async errors to the appropriate async handlers.
+  void waitAndThrow();
+
+  /// Flushes unconsumed async errors to the appropriate async handlers.
+  void throwAsynchronous();
+
   /// Enqueues a kernel to liboffload.
   /// Kernel parameters like dependencies and range must be passed in advance by
   /// calling setKernelParameters.
@@ -96,6 +104,9 @@ class QueueImpl : public std::enable_shared_from_this<QueueImpl> {
   void setKernelParameters(std::vector<EventImplPtr> &&Events,
                            const detail::UnifiedRangeView &Range);
 
+  /// \return the async_handler associated with this queue.
+  const async_handler &getAsyncHandler() const { return MAsyncHandler; }
+
 private:
   // Queue features.
   ol_queue_handle_t MOffloadQueue = {};
@@ -109,11 +120,7 @@ class QueueImpl : public std::enable_shared_from_this<QueueImpl> {
   struct KernelSubmitInfo {
     EventImplPtr LastEvent;
     ol_kernel_launch_size_args_t Range;
-    // TODO: consider storing EventImplPtr here, it will work with plain handle
-    // only because submission is done within queue::submit call. Otherwise we
-    // need to ensure that event handle is still alive by keeping our own copy
-    // of EventImpl.
-    std::vector<ol_event_handle_t> DepEvents;
+    std::vector<EventImplPtr> DepEvents;
   };
   inline static thread_local KernelSubmitInfo MCurrentSubmitInfo = {};
 };
diff --git a/libsycl/src/detail/spinlock.hpp b/libsycl/src/detail/spinlock.hpp
new file mode 100644
index 0000000000000..a8898a4e68463
--- /dev/null
+++ b/libsycl/src/detail/spinlock.hpp
@@ -0,0 +1,47 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains the declaration of a lightweight spin lock.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBSYCL_SPINLOCK
+#define _LIBSYCL_SPINLOCK
+
+#include <sycl/__impl/detail/config.hpp>
+
+#include <atomic>
+#include <thread>
+
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+
+namespace detail {
+
+/// SpinLock is a synchronization primitive based on std::atomic_flag.
+/// It is trivially constructible and suitable for global object wrappers.
+class SpinLock {
+public:
+  bool try_lock() { return !MLock.test_and_set(std::memory_order_acquire); }
+
+  void lock() {
+    while (MLock.test_and_set(std::memory_order_acquire))
+      std::this_thread::yield();
+  }
+
+  void unlock() { MLock.clear(std::memory_order_release); }
+
+private:
+  std::atomic_flag MLock = ATOMIC_FLAG_INIT;
+};
+
+} // namespace detail
+
+_LIBSYCL_END_NAMESPACE_SYCL
+
+#endif // _LIBSYCL_SPINLOCK
diff --git a/libsycl/src/event.cpp b/libsycl/src/event.cpp
index c0276b519f173..1a88cef5f9b0d 100644
--- a/libsycl/src/event.cpp
+++ b/libsycl/src/event.cpp
@@ -7,11 +7,14 @@
 //===----------------------------------------------------------------------===//
 
 #include <sycl/__impl/event.hpp>
+#include <sycl/__impl/exception.hpp>
 
 #include <detail/event_impl.hpp>
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
+event::event() : impl(detail::EventImpl::createDefaultEvent()) {}
+
 backend event::get_backend() const noexcept { return impl->getBackend(); }
 
 void event::wait(const std::vector<event> &EventList) {
@@ -22,4 +25,30 @@ void event::wait(const std::vector<event> &EventList) {
 
 void event::wait() { impl->wait(); }
 
+void event::wait_and_throw() { impl->waitAndThrow(); }
+
+void event::wait_and_throw(const std::vector<event> &EventList) {
+  for (auto E : EventList) {
+    E.wait_and_throw();
+  }
+}
+
+std::vector<event> event::get_wait_list() {
+  const auto &WaitList = impl->getWaitList();
+  std::vector<event> Result;
+  Result.reserve(WaitList.size());
+
+  for (const auto &EventImpl : WaitList)
+    Result.push_back(detail::createSyclObjFromImpl<event>(EventImpl));
+
+  return Result;
+}
+
+template <typename Param>
+typename detail::is_event_profiling_info_desc_t<Param>
+event::get_profiling_info() const {
+  throw sycl::exception(make_error_code(errc::feature_not_supported),
+                        "Profiling features are not supported.");
+}
+
 _LIBSYCL_END_NAMESPACE_SYCL
diff --git a/libsycl/src/exception_list.cpp b/libsycl/src/exception_list.cpp
index 1c0a78bd33681..61409fd6db77f 100644
--- a/libsycl/src/exception_list.cpp
+++ b/libsycl/src/exception_list.cpp
@@ -6,10 +6,11 @@
 //
 //===----------------------------------------------------------------------===//
 
-// SYCL 2020 4.13.2. Exception class interface.
 #include <sycl/__impl/detail/config.hpp>
 #include <sycl/__impl/exception.hpp>
 
+#include <iostream>
+
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
 exception_list::size_type exception_list::size() const { return MList.size(); }
@@ -18,4 +19,24 @@ exception_list::iterator exception_list::begin() const { return MList.begin(); }
 
 exception_list::iterator exception_list::end() const { return MList.cend(); }
 
+void detail::addAsyncException(exception_list &List,
+                               const std::exception_ptr &Exception) {
+  List.MList.emplace_back(Exception);
+}
+
+void detail::defaultAsyncHandler(exception_list Exceptions) {
+  std::cerr << "Default async_handler caught exceptions:";
+  for (auto &EIt : Exceptions) {
+    try {
+      if (EIt) {
+        std::rethrow_exception(EIt);
+      }
+    } catch (const std::exception &E) {
+      std::cerr << "\n\t" << E.what();
+    }
+  }
+  std::cerr << std::endl;
+  std::terminate();
+}
+
 _LIBSYCL_END_NAMESPACE_SYCL
diff --git a/libsycl/src/queue.cpp b/libsycl/src/queue.cpp
index b57324219e46b..66ae2b4698595 100644
--- a/libsycl/src/queue.cpp
+++ b/libsycl/src/queue.cpp
@@ -35,6 +35,10 @@ bool queue::is_in_order() const { return impl->isInOrder(); }
 
 void queue::wait() { impl->wait(); }
 
+void queue::wait_and_throw() { impl->waitAndThrow(); }
+
+void queue::throw_asynchronous() { impl->throwAsynchronous(); }
+
 event queue::getLastEvent() {
   return detail::createSyclObjFromImpl<event>(impl->getLastEvent());
 }
diff --git a/libsycl/unittests/CMakeLists.txt b/libsycl/unittests/CMakeLists.txt
index 56bb28161737c..21cdbf32570e3 100644
--- a/libsycl/unittests/CMakeLists.txt
+++ b/libsycl/unittests/CMakeLists.txt
@@ -6,6 +6,7 @@ add_custom_target(check-sycl-unittests)
 
 add_subdirectory(mock)
 add_subdirectory(device_selector)
+add_subdirectory(event)
 add_subdirectory(platform)
 add_subdirectory(program_manager)
 add_subdirectory(queue)
diff --git a/libsycl/unittests/common/device_images.hpp b/libsycl/unittests/common/device_images.hpp
index 3c53c097a9e78..7402214d8f519 100644
--- a/libsycl/unittests/common/device_images.hpp
+++ b/libsycl/unittests/common/device_images.hpp
@@ -20,7 +20,8 @@
 #include <llvm/Frontend/Offloading/Utility.h>
 #include <llvm/Object/OffloadBinary.h>
 
-namespace sycl::unittest {
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+namespace unittests {
 
 inline llvm::object::OffloadBinary::OffloadingImage createSYCLImage(
     llvm::StringRef SymbolsBlob,
@@ -49,6 +50,7 @@ inline llvm::SmallString<0> createSYCLDeviceBinary(
   return llvm::object::OffloadBinary::write(Image);
 }
 
-} // namespace sycl::unittest
+} // namespace unittests
+_LIBSYCL_END_NAMESPACE_SYCL
 
 #endif // _LIBSYCL_UNITTESTS_COMMON_DEVICE_IMAGES_HPP
diff --git a/libsycl/unittests/common/scoped_binary_registration.hpp b/libsycl/unittests/common/scoped_binary_registration.hpp
new file mode 100644
index 0000000000000..bd97d6a582503
--- /dev/null
+++ b/libsycl/unittests/common/scoped_binary_registration.hpp
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// RAII helpers for registering and unregistering test device binaries.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBSYCL_UNITTESTS_COMMON_SCOPED_BINARY_REGISTRATION_HPP
+#define _LIBSYCL_UNITTESTS_COMMON_SCOPED_BINARY_REGISTRATION_HPP
+
+#include <common/device_images.hpp>
+
+#include <detail/program_manager.hpp>
+
+#include <array>
+#include <string>
+#include <utility>
+
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+namespace unittests {
+
+class ScopedBinaryRegistration {
+public:
+  explicit ScopedBinaryRegistration(llvm::ArrayRef<llvm::StringRef> KernelNames)
+      : MBinary(createSYCLDeviceBinary(KernelNames)) {
+    sycl::detail::ProgramAndKernelManager::getInstance().registerFatBin(
+        MBinary.data(), MBinary.size());
+  }
+
+  ~ScopedBinaryRegistration() {
+    sycl::detail::ProgramAndKernelManager::getInstance().unregisterFatBin(
+        MBinary.data(), MBinary.size());
+  }
+
+private:
+  llvm::SmallString<0> MBinary;
+};
+
+class ScopedKernelRegistration {
+public:
+  explicit ScopedKernelRegistration(std::string KernelName)
+      : MKernelName(std::move(KernelName)),
+        MRegistration(createKernelNames(MKernelName)) {}
+
+private:
+  static std::array<llvm::StringRef, 1>
+  createKernelNames(const std::string &KernelName) {
+    return {KernelName};
+  }
+
+  std::string MKernelName;
+  ScopedBinaryRegistration MRegistration;
+};
+
+} // namespace unittests
+_LIBSYCL_END_NAMESPACE_SYCL
+
+#endif // _LIBSYCL_UNITTESTS_COMMON_SCOPED_BINARY_REGISTRATION_HPP
diff --git a/libsycl/unittests/common/unittests_helper.hpp b/libsycl/unittests/common/unittests_helper.hpp
index a1d5c5d0ccd7f..a04995225f0e5 100644
--- a/libsycl/unittests/common/unittests_helper.hpp
+++ b/libsycl/unittests/common/unittests_helper.hpp
@@ -19,7 +19,6 @@
 #include <mock/helpers.hpp>
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
-
 namespace unittests {
 
 // This helper is not included to LiboffloadMock to keep LiboffloadMock isolated
diff --git a/libsycl/unittests/device_selector/get_device_preference.cpp b/libsycl/unittests/device_selector/get_device_preference.cpp
index b4b6ccfa7bcd6..79920b9611a10 100644
--- a/libsycl/unittests/device_selector/get_device_preference.cpp
+++ b/libsycl/unittests/device_selector/get_device_preference.cpp
@@ -6,12 +6,10 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include <common/device_images.hpp>
+#include <common/scoped_binary_registration.hpp>
 #include <common/unittests_helper.hpp>
 
 #include <detail/device_impl.hpp>
-#include <detail/program_manager.hpp>
-
 #include <sycl/__impl/detail/obj_utils.hpp>
 #include <sycl/__impl/device_selector.hpp>
 #include <sycl/sycl.hpp>
@@ -24,23 +22,6 @@ using namespace ::testing;
 
 namespace {
 
-class ScopedBinaryRegistration {
-public:
-  explicit ScopedBinaryRegistration(llvm::ArrayRef<llvm::StringRef> KernelNames)
-      : MBinary(sycl::unittest::createSYCLDeviceBinary(KernelNames)) {
-    sycl::detail::ProgramAndKernelManager::getInstance().registerFatBin(
-        MBinary.data(), MBinary.size());
-  }
-
-  ~ScopedBinaryRegistration() {
-    sycl::detail::ProgramAndKernelManager::getInstance().unregisterFatBin(
-        MBinary.data(), MBinary.size());
-  }
-
-private:
-  llvm::SmallString<0> MBinary;
-};
-
 class DeviceSelectorScoreTest : public ::testing::Test {
 protected:
   void SetUp() override {
@@ -138,7 +119,7 @@ TEST_F(DeviceSelectorScoreTest, TwoGpusOneCompatibleImage) {
       });
 
   std::array<llvm::StringRef, 1> KernelNames = {"kernel"};
-  ScopedBinaryRegistration Registration{KernelNames};
+  sycl::unittests::ScopedBinaryRegistration Registration{KernelNames};
 
   auto Devices = sycl::device::get_devices();
   ASSERT_EQ(Devices.size(), 2u);
diff --git a/libsycl/unittests/event/CMakeLists.txt b/libsycl/unittests/event/CMakeLists.txt
new file mode 100644
index 0000000000000..fab70b9c6537b
--- /dev/null
+++ b/libsycl/unittests/event/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_sycl_unittest(EventTests
+    async_handler.cpp
+    event.cpp
+)
diff --git a/libsycl/unittests/event/async_handler.cpp b/libsycl/unittests/event/async_handler.cpp
new file mode 100644
index 0000000000000..1989819020a53
--- /dev/null
+++ b/libsycl/unittests/event/async_handler.cpp
@@ -0,0 +1,152 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include <mock/helpers.hpp>
+
+#include <detail/event_impl.hpp>
+#include <detail/global_objects.hpp>
+
+#include <sycl/__impl/detail/obj_utils.hpp>
+#include <sycl/__impl/device.hpp>
+#include <sycl/__impl/event.hpp>
+#include <sycl/__impl/platform.hpp>
+#include <sycl/__impl/queue.hpp>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <system_error>
+#include <vector>
+
+using namespace sycl;
+using namespace ::testing;
+
+namespace {
+
+using EventImplPtr = std::shared_ptr<detail::EventImpl>;
+
+EventImplPtr createEventImplWithHandle(detail::PlatformImpl &PlatformImpl) {
+  ol_event_handle_t Handle = mock::createDummyHandle<ol_event_handle_t>();
+  return detail::EventImpl::createEventWithHandle(Handle, PlatformImpl, {});
+}
+
+template <typename FlushAction>
+void runAsyncExceptionFlushTest(mock::MockLiboffload &Mock,
+                                const char *ErrorMsg, FlushAction Flush) {
+  size_t NumExceptionsHandled = 0;
+  std::vector<std::error_code> ReportedCodes;
+  std::vector<std::string> ReportedMessages;
+  auto expectSingleRuntimeErrorContaining = [&](const char *ExpectedMessage) {
+    EXPECT_EQ(NumExceptionsHandled, 1u);
+    ASSERT_THAT(ReportedCodes, SizeIs(1));
+    ASSERT_THAT(ReportedMessages, SizeIs(1));
+    EXPECT_EQ(ReportedCodes.back(), make_error_code(errc::runtime));
+    EXPECT_THAT(ReportedMessages.back(), HasSubstr(ExpectedMessage));
+  };
+
+  async_handler Handler = [&](exception_list Exceptions) {
+    for (const auto &ExceptionPtr : Exceptions) {
+      ++NumExceptionsHandled;
+      try {
+        std::rethrow_exception(ExceptionPtr);
+      } catch (const sycl::exception &E) {
+        ReportedCodes.push_back(E.code());
+        ReportedMessages.emplace_back(E.what());
+      } catch (...) {
+        ADD_FAILURE() << "Unexpected exception type in async_handler";
+      }
+    }
+  };
+
+  auto Device = device(default_selector_v);
+  queue Q(Device, Handler);
+  auto QImpl = detail::getSyclObjImpl(Q);
+
+  platform P = Device.get_platform();
+  EventImplPtr EImpl = createEventImplWithHandle(*detail::getSyclObjImpl(P));
+  event E = detail::createSyclObjFromImpl<event>(EImpl);
+
+  detail::reportAsyncException(
+      QImpl, std::make_exception_ptr(
+                 exception(make_error_code(errc::runtime), ErrorMsg)));
+
+  Flush(Mock, Q, QImpl, E, EImpl);
+  expectSingleRuntimeErrorContaining(ErrorMsg);
+}
+
+} // namespace
+
+TEST(EventAsyncHandler, QueueWaitAndThrow) {
+  mock::MockWrapper Mock;
+  constexpr const char *QueueWaitAndThrowErrorMsg =
+      "queue wait_and_throw error";
+
+  runAsyncExceptionFlushTest(Mock.get(), QueueWaitAndThrowErrorMsg,
+                             [](mock::MockLiboffload &Mock, queue &Q,
+                                const auto &, const auto &, const auto &) {
+                               EXPECT_CALL(Mock, olSyncQueue(_)).Times(1);
+                               Q.wait_and_throw();
+                             });
+}
+
+TEST(EventAsyncHandler, EventWaitAndThrow) {
+  mock::MockWrapper Mock;
+  constexpr const char *EventWaitAndThrowErrorMsg =
+      "event wait_and_throw error";
+
+  runAsyncExceptionFlushTest(
+      Mock.get(), EventWaitAndThrowErrorMsg,
+      [](mock::MockLiboffload &Mock, queue &, const auto &, event &E,
+         EventImplPtr &EImpl) {
+        EXPECT_CALL(Mock, olSyncQueue(_)).Times(0);
+        EXPECT_CALL(Mock, olSyncEvent(EImpl->getHandle())).Times(1);
+        E.wait_and_throw();
+      });
+}
+
+TEST(EventAsyncHandler, QueueThrowAsynchronous) {
+  mock::MockWrapper Mock;
+  constexpr const char *QueueThrowAsynchronousErrorMsg =
+      "queue throw_asynchronous error";
+
+  runAsyncExceptionFlushTest(Mock.get(), QueueThrowAsynchronousErrorMsg,
+                             [](mock::MockLiboffload &Mock, queue &Q,
+                                const auto &, const auto &, const auto &) {
+                               EXPECT_CALL(Mock, olSyncQueue(_)).Times(0);
+                               Q.throw_asynchronous();
+                             });
+}
+
+TEST(EventAsyncHandler, DeadQueueFallsBackToDefaultAsyncHandler) {
+// EXPECT_DEATH is not supported on Windows.
+#if GTEST_HAS_DEATH_TEST
+  EXPECT_DEATH(
+      {
+        mock::MockWrapper Mock;
+        async_handler Handler = [](exception_list) {
+          ADD_FAILURE() << "Queue async_handler should not be called";
+        };
+        {
+          queue Q(device(default_selector_v), Handler);
+          auto QImpl = detail::getSyclObjImpl(Q);
+          detail::reportAsyncException(
+              QImpl,
+              std::make_exception_ptr(exception(make_error_code(errc::runtime),
+                                                "fallback async error")));
+        }
+
+        detail::flushAsyncExceptions();
+      },
+      "Default async_handler caught exceptions:\n\tfallback async error");
+#else
+  GTEST_SKIP() << "Death tests are not supported on this platform";
+#endif
+}
diff --git a/libsycl/unittests/event/event.cpp b/libsycl/unittests/event/event.cpp
new file mode 100644
index 0000000000000..c1081b64894ca
--- /dev/null
+++ b/libsycl/unittests/event/event.cpp
@@ -0,0 +1,139 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include <common/scoped_binary_registration.hpp>
+#include <mock/helpers.hpp>
+
+#include <detail/event_impl.hpp>
+#include <detail/queue_impl.hpp>
+
+#include <sycl/__impl/detail/obj_utils.hpp>
+#include <sycl/__impl/device.hpp>
+#include <sycl/__impl/event.hpp>
+#include <sycl/__impl/platform.hpp>
+#include <sycl/__impl/queue.hpp>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <vector>
+
+using namespace sycl;
+using namespace ::testing;
+
+using EventImplPtr = std::shared_ptr<detail::EventImpl>;
+
+static EventImplPtr
+createEventImplWithHandle(detail::PlatformImpl &PlatformImpl,
+                          std::vector<EventImplPtr> WaitList = {}) {
+  ol_event_handle_t Handle = mock::createDummyHandle<ol_event_handle_t>();
+  return detail::EventImpl::createEventWithHandle(Handle, PlatformImpl,
+                                                  std::move(WaitList));
+}
+
+// MockQueue to expose protected methods for testing
+class sycl::detail::MockQueue : public sycl::queue {
+public:
+  using sycl::queue::getLastEvent;
+  using sycl::queue::setKernelParameters;
+  using sycl::queue::sycl_kernel_launch;
+};
+
+TEST(Event, CopyMoveAssign) {
+  mock::MockWrapper Mock;
+
+  event Event;
+  const size_t Hash = std::hash<event>{}(Event);
+  event MovedConstructed(std::move(Event));
+  EXPECT_EQ(Hash, std::hash<event>{}(MovedConstructed));
+
+  event AssignedSource;
+  const size_t AssignedHash = std::hash<event>{}(AssignedSource);
+  event MoveAssigned;
+  MoveAssigned = std::move(AssignedSource);
+  EXPECT_EQ(AssignedHash, std::hash<event>{}(MoveAssigned));
+
+  event CopiedSource;
+  const size_t CopiedHash = std::hash<event>{}(CopiedSource);
+  event CopyConstructed(CopiedSource);
+  EXPECT_EQ(CopiedHash, std::hash<event>{}(CopiedSource));
+  EXPECT_EQ(CopiedHash, std::hash<event>{}(CopyConstructed));
+  EXPECT_EQ(CopiedSource, CopyConstructed);
+
+  event CopyAssigned;
+  CopyAssigned = CopiedSource;
+  EXPECT_EQ(CopiedHash, std::hash<event>{}(CopyAssigned));
+  EXPECT_EQ(CopiedSource, CopyAssigned);
+}
+
+TEST(Event, WaitAPIsForDefaultConstructedEvent) {
+  mock::MockWrapper Mock;
+
+  event Event;
+  EXPECT_CALL(Mock.get(), olSyncEvent(_)).Times(0);
+
+  EXPECT_NO_THROW(Event.wait());
+  EXPECT_NO_THROW(Event.wait_and_throw());
+  EXPECT_TRUE(Event.get_wait_list().empty());
+
+  std::vector<event> EventList = {Event};
+  EXPECT_NO_THROW(event::wait(EventList));
+  EXPECT_NO_THROW(event::wait_and_throw(EventList));
+}
+
+TEST(Event, GetWaitListWithSetKernelParametersAndLaunch) {
+  static constexpr char TestKernelWithDeps[] = "TestKernelWithDeps";
+  mock::MockWrapper Mock;
+  sycl::unittests::ScopedKernelRegistration Registration{TestKernelWithDeps};
+
+  platform P = device(default_selector_v).get_platform();
+  auto &PlatformImpl = *detail::getSyclObjImpl(P);
+
+  detail::MockQueue Q;
+
+  EventImplPtr Dep1Impl = createEventImplWithHandle(PlatformImpl);
+  EventImplPtr Dep2Impl = createEventImplWithHandle(PlatformImpl);
+  event Dep1 = detail::createSyclObjFromImpl<event>(Dep1Impl);
+  event Dep2 = detail::createSyclObjFromImpl<event>(Dep2Impl);
+
+  struct KernelData {
+    int Value = 42;
+  } Data;
+
+  EXPECT_CALL(Mock.get(), olLaunchKernel(_, _, _, _, _, 1, _, _))
+      .WillOnce([](ol_queue_handle_t Queue, ol_device_handle_t Device,
+                   ol_symbol_handle_t Kernel,
+                   const ol_kernel_launch_size_args_t *LaunchSizeArgs,
+                   const ol_kernel_launch_prop_t *Properties, size_t NumArgs,
+                   void **ArgPtrs, const size_t *ArgSizes) -> ol_result_t {
+        EXPECT_NE(Queue, nullptr);
+        EXPECT_NE(Device, nullptr);
+        EXPECT_NE(Kernel, nullptr);
+        return OL_SUCCESS;
+      });
+
+  std::vector<event> DepEvents = {Dep1, Dep2};
+
+  Q.setKernelParameters(DepEvents);
+  Q.sycl_kernel_launch<class TestKernelWithDeps>(TestKernelWithDeps, Data);
+  event KernelEvent = Q.getLastEvent();
+
+  std::vector<event> WaitList = KernelEvent.get_wait_list();
+  ASSERT_THAT(WaitList, SizeIs(2));
+  EXPECT_EQ(WaitList[0], Dep1);
+  EXPECT_EQ(WaitList[1], Dep2);
+
+  auto KernelEventImpl = detail::getSyclObjImpl(KernelEvent);
+  EXPECT_CALL(Mock.get(), olSyncEvent(KernelEventImpl->getHandle())).Times(1);
+  KernelEvent.wait();
+
+  EXPECT_CALL(Mock.get(), olSyncEvent(Dep1Impl->getHandle())).Times(1);
+  EXPECT_CALL(Mock.get(), olSyncEvent(Dep2Impl->getHandle())).Times(1);
+  event::wait(WaitList);
+}
diff --git a/libsycl/unittests/mock/helpers.cpp b/libsycl/unittests/mock/helpers.cpp
index 6bc89e1b5713f..72be7706b03b4 100644
--- a/libsycl/unittests/mock/helpers.cpp
+++ b/libsycl/unittests/mock/helpers.cpp
@@ -277,6 +277,13 @@ void mock::MockLiboffload::initDefault() {
         return OL_SUCCESS;
       });
 
+  ON_CALL(*this, olSyncEvent)
+      .WillByDefault([this](ol_event_handle_t Event) -> ol_result_t {
+        if (!Event)
+          return makeEmptyStrError(OL_ERRC_INVALID_NULL_HANDLE);
+        return OL_SUCCESS;
+      });
+
   ON_CALL(*this, olCreateEvent)
       .WillByDefault([this](ol_queue_handle_t Queue, ol_event_flags_t Flags,
                             ol_event_handle_t *Event) -> ol_result_t {
diff --git a/libsycl/unittests/mock/helpers.hpp b/libsycl/unittests/mock/helpers.hpp
index 793c687b7276b..9405ae0b9199f 100644
--- a/libsycl/unittests/mock/helpers.hpp
+++ b/libsycl/unittests/mock/helpers.hpp
@@ -107,6 +107,7 @@ class MockLiboffload {
   MOCK_METHOD(ol_result_t, olWaitEvents,
               (ol_queue_handle_t Queue, ol_event_handle_t *Events,
                size_t NumEvents));
+  MOCK_METHOD(ol_result_t, olSyncEvent, (ol_event_handle_t Event));
   MOCK_METHOD(ol_result_t, olCreateEvent,
               (ol_queue_handle_t Queue, ol_event_flags_t Flags,
                ol_event_handle_t *Event));
diff --git a/libsycl/unittests/mock/mock.cpp b/libsycl/unittests/mock/mock.cpp
index a4e66548c7f40..4cb8e6766612d 100644
--- a/libsycl/unittests/mock/mock.cpp
+++ b/libsycl/unittests/mock/mock.cpp
@@ -95,6 +95,10 @@ ol_result_t olLaunchKernel(ol_queue_handle_t Queue, ol_device_handle_t Device,
                                                   NumArgs, ArgPtrs, ArgSizes);
 }
 
+ol_result_t olSyncEvent(ol_event_handle_t Event) {
+  return mock::getMockLiboffload().olSyncEvent(Event);
+}
+
 ol_result_t olCreateEvent(ol_queue_handle_t Queue, ol_event_flags_t Flags,
                           ol_event_handle_t *Event) {
   return mock::getMockLiboffload().olCreateEvent(Queue, Flags, Event);
diff --git a/libsycl/unittests/program_manager/register_and_unregister.cpp b/libsycl/unittests/program_manager/register_and_unregister.cpp
index f266e98a02e14..648e1ef6cfc7f 100644
--- a/libsycl/unittests/program_manager/register_and_unregister.cpp
+++ b/libsycl/unittests/program_manager/register_and_unregister.cpp
@@ -32,7 +32,7 @@ struct MockProgramAndKernelManager : public detail::ProgramAndKernelManager {
 TEST(ProgramAndKernelManager, CheckUnsupportedVersionOfFatbin) {
   std::array<llvm::StringRef, 1> KernelNames = {"kernel"};
   llvm::SmallString<0> Binary =
-      sycl::unittest::createSYCLDeviceBinary(KernelNames);
+      sycl::unittests::createSYCLDeviceBinary(KernelNames);
 
   llvm::MemoryBufferRef MBR(
       llvm::StringRef(static_cast<const char *>(Binary.data()), Binary.size()),
@@ -59,8 +59,8 @@ TEST(ProgramAndKernelManager, CheckUnsupportedVersionOfFatbin) {
 TEST(ProgramAndKernelManager, CheckUnsupportedVersionOfImage) {
   std::array<llvm::StringRef, 1> KernelNames = {"kernel"};
   llvm::SmallString<0> IncompatibleImageBinary =
-      sycl::unittest::createSYCLDeviceBinary(KernelNames,
-                                             llvm::object::IMG_Bitcode);
+      sycl::unittests::createSYCLDeviceBinary(KernelNames,
+                                              llvm::object::IMG_Bitcode);
 
   MockProgramAndKernelManager Manager;
 
@@ -75,8 +75,8 @@ TEST(ProgramAndKernelManager, CheckUnsupportedVersionOfImage) {
                 Property(&sycl::exception::code, Eq(sycl::errc::runtime)))));
 
   llvm::SmallString<0> CompatibleImageBinary =
-      sycl::unittest::createSYCLDeviceBinary(KernelNames,
-                                             llvm::object::IMG_SPIRV);
+      sycl::unittests::createSYCLDeviceBinary(KernelNames,
+                                              llvm::object::IMG_SPIRV);
   EXPECT_NO_THROW(Manager.registerFatBin(CompatibleImageBinary.data(),
                                          CompatibleImageBinary.size()));
   EXPECT_NO_THROW(Manager.unregisterFatBin(CompatibleImageBinary.data(),
@@ -95,8 +95,8 @@ TEST(ProgramAndKernelManager, CheckRegisterAndUnregister) {
   llvm::offloading::sycl::writeSymbolTable(Image2Kernels, Symbols[1]);
 
   llvm::SmallVector<llvm::object::OffloadBinary::OffloadingImage, 2> Images;
-  Images.push_back(sycl::unittest::createSYCLImage(Symbols[0]));
-  Images.push_back(sycl::unittest::createSYCLImage(Symbols[1]));
+  Images.push_back(sycl::unittests::createSYCLImage(Symbols[0]));
+  Images.push_back(sycl::unittests::createSYCLImage(Symbols[1]));
 
   llvm::SmallString<0> Binary = llvm::object::OffloadBinary::write(Images);
 
diff --git a/libsycl/unittests/queue/sycl_kernel_launch.cpp b/libsycl/unittests/queue/sycl_kernel_launch.cpp
index 653dc7306d623..7423dbbfff4b1 100644
--- a/libsycl/unittests/queue/sycl_kernel_launch.cpp
+++ b/libsycl/unittests/queue/sycl_kernel_launch.cpp
@@ -6,47 +6,19 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include <common/device_images.hpp>
+#include <common/scoped_binary_registration.hpp>
 #include <mock/helpers.hpp>
 
-#include <detail/program_manager.hpp>
-
 #include <sycl/__impl/queue.hpp>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
-#include <array>
 #include <cstring>
-#include <string>
 
 using namespace sycl;
 using namespace ::testing;
 
-namespace {
-
-class ScopedKernelRegistration {
-public:
-  explicit ScopedKernelRegistration(std::string KernelName)
-      : MKernelName(std::move(KernelName)) {
-    std::array<llvm::StringRef, 1> KernelNames = {MKernelName};
-    MBinary = sycl::unittest::createSYCLDeviceBinary(KernelNames);
-    sycl::detail::ProgramAndKernelManager::getInstance().registerFatBin(
-        MBinary.data(), MBinary.size());
-  }
-
-  ~ScopedKernelRegistration() {
-    sycl::detail::ProgramAndKernelManager::getInstance().unregisterFatBin(
-        MBinary.data(), MBinary.size());
-  }
-
-private:
-  std::string MKernelName;
-  llvm::SmallString<0> MBinary;
-};
-
-} // namespace
-
 class sycl::detail::MockQueue : public sycl::queue {
 public:
   using sycl::queue::sycl_kernel_launch;
@@ -60,7 +32,7 @@ struct KernelData {
 
 TEST(Queue, KernelLaunch) {
   mock::MockWrapper Mock;
-  ScopedKernelRegistration Registration{"TestKernel"};
+  sycl::unittests::ScopedKernelRegistration Registration{"TestKernel"};
   sycl::detail::MockQueue Q;
   KernelData Data{};
   Data.Arr[0] = 'A';



More information about the llvm-commits mailing list