diff --git a/sycl/source/detail/queue_impl.cpp b/sycl/source/detail/queue_impl.cpp index fbfadfc8c39e8..939a60b90fda0 100644 --- a/sycl/source/detail/queue_impl.cpp +++ b/sycl/source/detail/queue_impl.cpp @@ -615,14 +615,21 @@ EventImplPtr queue_impl::submit_barrier_direct_impl( /*SchedulerBypass*/ true}; } - if (EventForReuse) { + if (EventForReuse || !CallerNeedsEvent) { // Current limitation: reusable events require scheduler bypass so that // the barrier can be submitted directly to the backend with the reusable // event's handle as the output event. Scheduler bypass is not possible // when dependencies include host tasks or cross-context dependencies. - throw sycl::exception(sycl::make_error_code(errc::invalid), - "An event cannot be enqueued for signaling behind " - "a command which is not enqueued in the backend."); + // + // This limitation applies to both: enqueue_signal_event and + // enqueue_wait_event(s). + // + // The !CallerNeedsEvent condition is used to detect the + // enqueue_wait_event(s) function calls. + throw sycl::exception( + sycl::make_error_code(errc::invalid), + "An event cannot be enqueued for signaling or waiting " + "behind a command which is not enqueued in the backend."); } std::unique_ptr CommandGroup; diff --git a/sycl/source/reusable_events.cpp b/sycl/source/reusable_events.cpp index 642edd4e298d8..b2e41c0e7bd11 100644 --- a/sycl/source/reusable_events.cpp +++ b/sycl/source/reusable_events.cpp @@ -56,10 +56,31 @@ __SYCL_EXPORT sycl::event make_event(const sycl::context &ctxt, return RetEvent; } +static void CheckEventAndThrow(detail::event_impl &EventImpl, + detail::context_impl &ContextImpl) { + if (EventImpl.isHost()) { + throw sycl::exception(sycl::make_error_code(errc::invalid), + "Host events cannot be enqueued for waiting."); + } + + // Current limitation: + // The queue and an event need to be in the same context. The reason + // is, that cross-context dependencies use host tasks, and the wait + // command might be queued in the runtime. This flow is currently + // not supported by the Reusable Events APIs. + if (&EventImpl.getContextImpl() != &ContextImpl) { + throw sycl::exception(sycl::make_error_code(errc::invalid), + "Event context must match the queue context."); + } +} + } // namespace detail __SYCL_EXPORT void enqueue_wait_event(sycl::queue q, const event &evt) { detail::queue_impl &QueueImpl = *sycl::detail::getSyclObjImpl(q); + detail::event_impl &EventImpl = *sycl::detail::getSyclObjImpl(evt); + + detail::CheckEventAndThrow(EventImpl, QueueImpl.getContextImpl()); QueueImpl.submit_barrier_direct_without_event( sycl::span(&evt, 1), detail::CGType::BarrierWaitlist, @@ -70,6 +91,11 @@ __SYCL_EXPORT void enqueue_wait_events(sycl::queue q, const std::vector &evts) { detail::queue_impl &QueueImpl = *sycl::detail::getSyclObjImpl(q); + for (const sycl::event &evt : evts) { + detail::CheckEventAndThrow(*sycl::detail::getSyclObjImpl(evt), + QueueImpl.getContextImpl()); + } + QueueImpl.submit_barrier_direct_without_event( evts, detail::CGType::BarrierWaitlist, detail::code_location::current()); } @@ -90,15 +116,7 @@ __SYCL_EXPORT void enqueue_signal_event(sycl::queue q, event &evt) { "on a queue which is recording a graph."); } - detail::context_impl &QueueContextImpl = - *sycl::detail::getSyclObjImpl(q.get_context()); - - detail::context_impl &EventContextImpl = EventImpl.getContextImpl(); - - if (&QueueContextImpl != &EventContextImpl) { - throw sycl::exception(sycl::make_error_code(errc::invalid), - "Event context must match the queue context."); - } + detail::CheckEventAndThrow(EventImpl, QueueImpl.getContextImpl()); // An IPC event cannot be signaled on a profiling-enabled queue. if (EventImpl.isIPCEnabled() && QueueImpl.MIsProfilingEnabled) { diff --git a/sycl/unittests/Extensions/ReusableEvents.cpp b/sycl/unittests/Extensions/ReusableEvents.cpp index f572f25e8fcba..4080a14ec752f 100644 --- a/sycl/unittests/Extensions/ReusableEvents.cpp +++ b/sycl/unittests/Extensions/ReusableEvents.cpp @@ -443,7 +443,9 @@ TEST_F(ReusableEventsTest, EventInDependsOn) { Queue.wait(); } -// Cross-context events with wait +// Cross-context event with wait. +// Current limitation is that the cross-context wait +// is not supported. TEST_F(ReusableEventsTest, CrossContextEventWait) { mock::getCallbacks().set_replace_callback("urDeviceGet", &redefinedUrDeviceGet); @@ -467,26 +469,69 @@ TEST_F(ReusableEventsTest, CrossContextEventWait) { auto event = syclex::make_event(Ctx1); - mock::getCallbacks().set_replace_callback( - "urEnqueueEventsWaitWithBarrierExt", - &redefinedUrEnqueueEventsWaitWithBarrierExt_signal); + bool exception = false; - syclex::enqueue_signal_event(Queue1, event); + // Current limitation is that an event from different context + // cannot be used with enqueue_wait_event + try { + syclex::enqueue_wait_event(Queue2, event); + } catch (sycl::exception const &e) { + exception = true; + EXPECT_EQ(e.code(), sycl::errc::invalid); + EXPECT_STREQ(e.what(), "Event context must match the queue context."); + } - EXPECT_EQ(RedefinedUrEnqueueEventsWaitWithBarrierExt_signal_counter, 1); + EXPECT_TRUE(exception); - ExpectedNumEventsInWaitList = 1; - mock::getCallbacks().set_replace_callback( - "urEnqueueEventsWaitWithBarrierExt", - &redefinedUrEnqueueEventsWaitWithBarrierExt_wait); + Queue1.wait(); + Queue2.wait(); +} - // Event from different context should still work with enqueue_wait_event - EXPECT_NO_THROW({ syclex::enqueue_wait_event(Queue2, event); }); +// Cross-context events with wait (multiple events). +// Current limitation is that the cross-context wait +// is not supported. +TEST_F(ReusableEventsTest, CrossContextEventsWait) { + mock::getCallbacks().set_replace_callback("urDeviceGet", + &redefinedUrDeviceGet); + mock::getCallbacks().set_replace_callback("urDeviceRelease", + &redefinedUrDeviceRelease); - EXPECT_EQ(RedefinedUrEnqueueEventsWaitWithBarrierExt_wait_counter, 1); + sycl::platform Plt = sycl::platform(); + auto Devices = Plt.get_devices(); - Queue1.wait(); - Queue2.wait(); + if (Devices.size() < 2) { + GTEST_SKIP() << "Need at least 2 devices for this test"; + } + + const sycl::device Dev1 = Devices[0]; + const sycl::device Dev2 = Devices[1]; + sycl::context Ctx1{Dev1}; + sycl::context Ctx2{Dev2}; + + sycl::queue Queue{Ctx1, Dev1}; + + // Check if the exception is thrown if one of the events + // has different context than the queue. + auto event1 = syclex::make_event(Ctx1); + auto event2 = syclex::make_event(Ctx2); + + std::vector events{event1, event2}; + + bool exception = false; + + // Current limitation is that an event from different context + // cannot be used with enqueue_wait_event + try { + syclex::enqueue_wait_events(Queue, events); + } catch (sycl::exception const &e) { + exception = true; + EXPECT_EQ(e.code(), sycl::errc::invalid); + EXPECT_STREQ(e.what(), "Event context must match the queue context."); + } + + EXPECT_TRUE(exception); + + Queue.wait(); } // Cross-context make_event and signal event - not allowed