Repository: intel/llvm
Pull request #17943: [SYCL] Remove OoO Emulation
Author: login: jbrodman
Created: 2025-04-09T20:38:03Z | Merged: 2025-04-15T16:36:36Z

Remove the legacy implementation of emulation for OoO queues. This code predated the PI and UR layers in DPC++ because fpga OpenCL did not support OoO queues. Today, this type of thing should be done by the backend compiler or UR layer.



Diff:
diff --git a/sycl/source/detail/queue_impl.cpp b/sycl/source/detail/queue_impl.cpp
index b6ba59bdf7ed7..58f4b0631cdb8 100644
--- a/sycl/source/detail/queue_impl.cpp
+++ b/sycl/source/detail/queue_impl.cpp
@@ -60,8 +60,7 @@ template <>
 uint32_t queue_impl::get_info<info::queue::reference_count>() const {
   ur_result_t result = UR_RESULT_SUCCESS;
   getAdapter()->call<UrApiKind::urQueueGetInfo>(
-      MQueues[0], UR_QUEUE_INFO_REFERENCE_COUNT, sizeof(result), &result,
-      nullptr);
+      MQueue, UR_QUEUE_INFO_REFERENCE_COUNT, sizeof(result), &result, nullptr);
   return result;
 }
 
@@ -303,55 +302,14 @@ void queue_impl::addEvent(const event &Event) {
   const EventImplPtr &EImpl = getSyclObjImpl(Event);
   assert(EImpl && "Event implementation is missing");
   auto *Cmd = static_cast<Command *>(EImpl->getCommand());
-  if (!Cmd) {
-    // if there is no command on the event, we cannot track it with MEventsWeak
-    // as that will leave it with no owner. Track in MEventsShared only if we're
-    // unable to call urQueueFinish during wait.
-    if (MEmulateOOO)
-      addSharedEvent(Event);
-  }
-  // As long as the queue supports urQueueFinish we only need to store events
-  // for undiscarded, unenqueued commands and host tasks.
-  else if (MEmulateOOO ||
-           (EImpl->getHandle() == nullptr && !EImpl->isDiscarded())) {
+  if (Cmd != nullptr && EImpl->getHandle() == nullptr &&
+      !EImpl->isDiscarded()) {
     std::weak_ptr<event_impl> EventWeakPtr{EImpl};
     std::lock_guard<std::mutex> Lock{MMutex};
     MEventsWeak.push_back(std::move(EventWeakPtr));
   }
 }
 
-/// addSharedEvent - queue_impl tracks events with weak pointers
-/// but some events have no other owner. In this case,
-/// addSharedEvent will have the queue track the events via a shared pointer.
-void queue_impl::addSharedEvent(const event &Event) {
-  assert(MEmulateOOO);
-  std::lock_guard<std::mutex> Lock(MMutex);
-  // Events stored in MEventsShared are not released anywhere else aside from
-  // calls to queue::wait/wait_and_throw, which a user application might not
-  // make, and ~queue_impl(). If the number of events grows large enough,
-  // there's a good chance that most of them are already completed and ownership
-  // of them can be released.
-  const size_t EventThreshold = 128;
-  if (MEventsShared.size() >= EventThreshold) {
-    // Generally, the vector is ordered so that the oldest events are in the
-    // front and the newer events are in the end.  So, search to find the first
-    // event that isn't yet complete.  All the events prior to that can be
-    // erased. This could leave some few events further on that have completed
-    // not yet erased, but that is OK.  This cleanup doesn't have to be perfect.
-    // This also keeps the algorithm linear rather than quadratic because it
-    // doesn't continually recheck things towards the back of the list that
-    // really haven't had time to complete.
-    MEventsShared.erase(
-        MEventsShared.begin(),
-        std::find_if(
-            MEventsShared.begin(), MEventsShared.end(), [](const event &E) {
-              return E.get_info<info::event::command_execution_status>() !=
-                     info::event_command_status::complete;
-            }));
-  }
-  MEventsShared.push_back(Event);
-}
-
 event queue_impl::submit_impl(const detail::type_erased_cgfo_ty &CGF,
                               const std::shared_ptr<queue_impl> &Self,
                               const std::shared_ptr<queue_impl> &PrimaryQueue,
@@ -487,9 +445,7 @@ event queue_impl::submitMemOpHelper(const std::shared_ptr<queue_impl> &Self,
                                                 : MExtGraphDeps.LastEventPtr;
         EventToStoreIn = EventImpl;
       }
-      // Track only if we won't be able to handle it with urQueueFinish.
-      if (MEmulateOOO)
-        addSharedEvent(ResEvent);
+
       return discard_or_return(ResEvent);
     }
   }
@@ -597,10 +553,9 @@ void queue_impl::wait(const detail::code_location &CodeLoc) {
 
     // Additionally, we can clean up the event lists that we would have
     // otherwise cleared.
-    if (!MEventsWeak.empty() || !MEventsShared.empty()) {
+    if (!MEventsWeak.empty()) {
       std::lock_guard<std::mutex> Lock(MMutex);
       MEventsWeak.clear();
-      MEventsShared.clear();
     }
     if (!MStreamsServiceEvents.empty()) {
       std::lock_guard<std::mutex> Lock(MStreamsServiceEventsMutex);
@@ -609,11 +564,9 @@ void queue_impl::wait(const detail::code_location &CodeLoc) {
   }
 
   std::vector<std::weak_ptr<event_impl>> WeakEvents;
-  std::vector<event> SharedEvents;
   {
     std::lock_guard<std::mutex> Lock(MMutex);
     WeakEvents.swap(MEventsWeak);
-    SharedEvents.swap(MEventsShared);
 
     {
       std::lock_guard<std::mutex> RequestLock(MMissedCleanupRequestsMtx);
@@ -627,27 +580,19 @@ void queue_impl::wait(const detail::code_location &CodeLoc) {
   // directly. Otherwise, only wait for unenqueued or host task events, starting
   // from the latest submitted task in order to minimize total amount of calls,
   // then handle the rest with urQueueFinish.
-  const bool SupportsPiFinish = !MEmulateOOO;
   for (auto EventImplWeakPtrIt = WeakEvents.rbegin();
        EventImplWeakPtrIt != WeakEvents.rend(); ++EventImplWeakPtrIt) {
     if (std::shared_ptr<event_impl> EventImplSharedPtr =
             EventImplWeakPtrIt->lock()) {
       // A nullptr UR event indicates that urQueueFinish will not cover it,
       // either because it's a host task event or an unenqueued one.
-      if (!SupportsPiFinish || nullptr == EventImplSharedPtr->getHandle()) {
+      if (nullptr == EventImplSharedPtr->getHandle()) {
         EventImplSharedPtr->wait(EventImplSharedPtr);
       }
     }
   }
-  if (SupportsPiFinish) {
-    const AdapterPtr &Adapter = getAdapter();
-    Adapter->call<UrApiKind::urQueueFinish>(getHandleRef());
-    assert(SharedEvents.empty() && "Queues that support calling piQueueFinish "
-                                   "shouldn't have shared events");
-  } else {
-    for (event &Event : SharedEvents)
-      Event.wait();
-  }
+  const AdapterPtr &Adapter = getAdapter();
+  Adapter->call<UrApiKind::urQueueFinish>(getHandleRef());
 
   std::vector<EventImplPtr> StreamsServiceEvents;
   {
@@ -727,7 +672,7 @@ ur_native_handle_t queue_impl::getNative(int32_t &NativeHandleDesc) const {
                                       nullptr, nullptr};
   UrNativeDesc.pNativeData = &NativeHandleDesc;
 
-  Adapter->call<UrApiKind::urQueueGetNativeHandle>(MQueues[0], &UrNativeDesc,
+  Adapter->call<UrApiKind::urQueueGetNativeHandle>(MQueue, &UrNativeDesc,
                                                    &Handle);
   if (getContextImplPtr()->getBackend() == backend::opencl)
     __SYCL_OCL_CALL(clRetainCommandQueue, ur::cast<cl_command_queue>(Handle));
@@ -756,18 +701,13 @@ bool queue_impl::ext_oneapi_empty() const {
   // Check the status of the backend queue if this is not a host queue.
   ur_bool_t IsReady = false;
   getAdapter()->call<UrApiKind::urQueueGetInfo>(
-      MQueues[0], UR_QUEUE_INFO_EMPTY, sizeof(IsReady), &IsReady, nullptr);
+      MQueue, UR_QUEUE_INFO_EMPTY, sizeof(IsReady), &IsReady, nullptr);
   if (!IsReady)
     return false;
 
   // We may have events like host tasks which are not submitted to the backend
   // queue so we need to get their status separately.
   std::lock_guard<std::mutex> Lock(MMutex);
-  for (event Event : MEventsShared)
-    if (Event.get_info<info::event::command_execution_status>() !=
-        info::event_command_status::complete)
-      return false;
-
   for (auto EventImplWeakPtrIt = MEventsWeak.begin();
        EventImplWeakPtrIt != MEventsWeak.end(); ++EventImplWeakPtrIt)
     if (std::shared_ptr<event_impl> EventImplSharedPtr =
diff --git a/sycl/source/detail/queue_impl.hpp b/sycl/source/detail/queue_impl.hpp
index b4725ae24a111..81e5fe3ec9526 100644
--- a/sycl/source/detail/queue_impl.hpp
+++ b/sycl/source/detail/queue_impl.hpp
@@ -166,7 +166,7 @@ class queue_impl {
     }
     const QueueOrder QOrder =
         MIsInorder ? QueueOrder::Ordered : QueueOrder::OOO;
-    MQueues.push_back(createQueue(QOrder));
+    MQueue = createQueue(QOrder);
     // This section is the second part of the instrumentation that uses the
     // tracepoint information and notifies
 
@@ -191,13 +191,13 @@ class queue_impl {
                             "discard_events and enable_profiling.");
     }
 
-    MQueues.push_back(UrQueue);
+    MQueue = UrQueue;
 
     ur_device_handle_t DeviceUr{};
     const AdapterPtr &Adapter = getAdapter();
     // TODO catch an exception and put it to list of asynchronous exceptions
     Adapter->call<UrApiKind::urQueueGetInfo>(
-        MQueues[0], UR_QUEUE_INFO_DEVICE, sizeof(DeviceUr), &DeviceUr, nullptr);
+        MQueue, UR_QUEUE_INFO_DEVICE, sizeof(DeviceUr), &DeviceUr, nullptr);
     MDevice = MContext->findMatchingDeviceImpl(DeviceUr);
     if (MDevice == nullptr) {
       throw sycl::exception(
@@ -264,7 +264,7 @@ class queue_impl {
       destructorNotification();
 #endif
       throw_asynchronous();
-      getAdapter()->call<UrApiKind::urQueueRelease>(MQueues[0]);
+      getAdapter()->call<UrApiKind::urQueueRelease>(MQueue);
     } catch (std::exception &e) {
       __SYCL_REPORT_EXCEPTION_TO_STREAM("exception in ~queue_impl", e);
     }
@@ -274,7 +274,7 @@ class queue_impl {
 
   cl_command_queue get() {
     ur_native_handle_t nativeHandle = 0;
-    getAdapter()->call<UrApiKind::urQueueGetNativeHandle>(MQueues[0], nullptr,
+    getAdapter()->call<UrApiKind::urQueueGetNativeHandle>(MQueue, nullptr,
                                                           &nativeHandle);
     __SYCL_OCL_CALL(clRetainCommandQueue, ur::cast<cl_command_queue>(nativeHandle));
     return ur::cast<cl_command_queue>(nativeHandle);
@@ -322,9 +322,7 @@ class queue_impl {
                             "flush cannot be called for a queue which is "
                             "recording to a command graph.");
     }
-    for (const auto &queue : MQueues) {
-      getAdapter()->call<UrApiKind::urQueueFlush>(queue);
-    }
+    getAdapter()->call<UrApiKind::urQueueFlush>(MQueue);
   }
 
   /// Submits a command group function object to the queue, in order to be
@@ -540,62 +538,15 @@ class queue_impl {
               .get_index();
       Properties.pNext = &IndexProperties;
     }
-    ur_result_t Error = Adapter->call_nocheck<UrApiKind::urQueueCreate>(
-        Context, Device, &Properties, &Queue);
-
-    // If creating out-of-order queue failed and this property is not
-    // supported (for example, on FPGA), it will return
-    // UR_RESULT_ERROR_INVALID_QUEUE_PROPERTIES and will try to create in-order
-    // queue.
-    if (!MEmulateOOO && Error == UR_RESULT_ERROR_INVALID_QUEUE_PROPERTIES) {
-      MEmulateOOO = true;
-      Queue = createQueue(QueueOrder::Ordered);
-    } else {
-      Adapter->checkUrResult(Error);
-    }
+    Adapter->call<UrApiKind::urQueueCreate>(Context, Device, &Properties,
+                                            &Queue);
 
     return Queue;
   }
 
-  /// \return a raw UR handle for a free queue. The returned handle is not
-  /// retained. It is caller responsibility to make sure queue is still alive.
-  ur_queue_handle_t &getExclusiveUrQueueHandleRef() {
-    ur_queue_handle_t *PIQ = nullptr;
-    bool ReuseQueue = false;
-    {
-      std::lock_guard<std::mutex> Lock(MMutex);
-
-      // To achieve parallelism for FPGA with in order execution model with
-      // possibility of two kernels to share data with each other we shall
-      // create a queue for every kernel enqueued.
-      if (MQueues.size() < MaxNumQueues) {
-        MQueues.push_back({});
-        PIQ = &MQueues.back();
-      } else {
-        // If the limit of OpenCL queues is going to be exceeded - take the
-        // earliest used queue, wait until it finished and then reuse it.
-        PIQ = &MQueues[MNextQueueIdx];
-        MNextQueueIdx = (MNextQueueIdx + 1) % MaxNumQueues;
-        ReuseQueue = true;
-      }
-    }
-
-    if (!ReuseQueue)
-      *PIQ = createQueue(QueueOrder::Ordered);
-    else
-      getAdapter()->call<UrApiKind::urQueueFinish>(*PIQ);
-
-    return *PIQ;
-  }
-
   /// \return a raw UR queue handle. The returned handle is not retained. It
   /// is caller responsibility to make sure queue is still alive.
-  ur_queue_handle_t &getHandleRef() {
-    if (!MEmulateOOO)
-      return MQueues[0];
-
-    return getExclusiveUrQueueHandleRef();
-  }
+  ur_queue_handle_t &getHandleRef() { return MQueue; }
 
   /// \return true if the queue was constructed with property specified by
   /// PropertyT.
@@ -998,19 +949,12 @@ class queue_impl {
   /// Events without data dependencies (such as USM) need an owner,
   /// additionally, USM operations are not added to the scheduler command graph,
   /// queue is the only owner on the runtime side.
-  std::vector<event> MEventsShared;
   exception_list MExceptions;
   const async_handler MAsyncHandler;
   const property_list MPropList;
 
   /// List of queues created for FPGA device from a single SYCL queue.
-  std::vector<ur_queue_handle_t> MQueues;
-  /// Iterator through MQueues.
-  size_t MNextQueueIdx = 0;
-
-  /// Indicates that a native out-of-order queue could not be created and we
-  /// need to emulate it with multiple native in-order queues.
-  bool MEmulateOOO = false;
+  ur_queue_handle_t MQueue;
 
   // Access should be guarded with MMutex
   struct DependencyTrackingItems {
diff --git a/sycl/unittests/queue/CMakeLists.txt b/sycl/unittests/queue/CMakeLists.txt
index 844e2509337fe..1045a1865cb29 100644
--- a/sycl/unittests/queue/CMakeLists.txt
+++ b/sycl/unittests/queue/CMakeLists.txt
@@ -1,6 +1,5 @@
 add_sycl_unittest(QueueTests OBJECT
   DeviceCheck.cpp
-  EventClear.cpp
   Hash.cpp
   USM.cpp
   Wait.cpp
diff --git a/sycl/unittests/queue/EventClear.cpp b/sycl/unittests/queue/EventClear.cpp
deleted file mode 100644
index 2000235b1f15d..0000000000000
--- a/sycl/unittests/queue/EventClear.cpp
+++ /dev/null
@@ -1,124 +0,0 @@
-//==------------------ EventClear.cpp --- queue unit tests -----------------==//
-//
-// 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 <detail/context_impl.hpp>
-#include <gtest/gtest.h>
-#include <helpers/UrMock.hpp>
-#include <sycl/sycl.hpp>
-
-using namespace sycl;
-
-struct TestCtx {
-  TestCtx(context &Ctx) : Ctx{Ctx} {};
-
-  context &Ctx;
-  int NEventsWaitedFor = 0;
-  int EventReferenceCount = 0;
-};
-
-std::unique_ptr<TestCtx> TestContext;
-
-const int ExpectedEventThreshold = 128;
-
-ur_result_t redefinedQueueCreate(void *pParams) {
-  auto params = *static_cast<ur_queue_create_params_t *>(pParams);
-  assert(*params.ppProperties);
-  // Use in-order queues to force storing events for calling wait on them,
-  // rather than calling urQueueFinish.
-  if ((*params.ppProperties)->flags &
-      UR_QUEUE_FLAG_OUT_OF_ORDER_EXEC_MODE_ENABLE) {
-    return UR_RESULT_ERROR_INVALID_QUEUE_PROPERTIES;
-  }
-  return UR_RESULT_SUCCESS;
-}
-
-ur_result_t redefinedEventsWait(void *) {
-  ++TestContext->NEventsWaitedFor;
-  return UR_RESULT_SUCCESS;
-}
-
-ur_result_t redefinedEventGetInfoAfter(void *pParams) {
-  auto params = *static_cast<ur_event_get_info_params_t *>(pParams);
-  EXPECT_EQ(*params.ppropName, UR_EVENT_INFO_COMMAND_EXECUTION_STATUS)
-      << "Unexpected event info requested";
-  // Report first half of events as complete.
-  // Report second half of events as running.
-  // This is important, because removal algorithm assumes that
-  // events are likely to be removed oldest first, and stops removing
-  // at the first non-completed event.
-  static int Counter = 0;
-  auto *Result = reinterpret_cast<ur_event_status_t *>(*params.ppPropValue);
-  *Result = (Counter < (ExpectedEventThreshold / 2)) ? UR_EVENT_STATUS_COMPLETE
-                                                     : UR_EVENT_STATUS_RUNNING;
-  Counter++;
-  return UR_RESULT_SUCCESS;
-}
-
-ur_result_t redefinedEventRetain(void *) {
-  ++TestContext->EventReferenceCount;
-  return UR_RESULT_SUCCESS;
-}
-
-ur_result_t redefinedEventRelease(void *) {
-  --TestContext->EventReferenceCount;
-  return UR_RESULT_SUCCESS;
-}
-
-void prepareUrMock(unittest::UrMock<> &Mock) {
-  mock::getCallbacks().set_before_callback("urQueueCreate",
-                                           &redefinedQueueCreate);
-  mock::getCallbacks().set_before_callback("urEventWait", &redefinedEventsWait);
-  mock::getCallbacks().set_after_callback("urEventGetInfo",
-                                          &redefinedEventGetInfoAfter);
-  mock::getCallbacks().set_before_callback("urEventRetain",
-                                           &redefinedEventRetain);
-  mock::getCallbacks().set_before_callback("urEventRelease",
-                                           &redefinedEventRelease);
-}
-
-// Check that the USM events are cleared from the queue upon call to wait(),
-// so that they are not waited for multiple times.
-TEST(QueueEventClear, ClearOnQueueWait) {
-  sycl::unittest::UrMock<> Mock;
-  sycl::platform Plt = sycl::platform();
-  prepareUrMock(Mock);
-
-  context Ctx{Plt.get_devices()[0]};
-  TestContext.reset(new TestCtx(Ctx));
-  queue Q{Ctx, default_selector()};
-
-  unsigned char *HostAlloc = (unsigned char *)malloc_host(1, Ctx);
-  TestContext->EventReferenceCount = 1;
-  Q.memset(HostAlloc, 42, 1);
-  Q.wait();
-  ASSERT_EQ(TestContext->NEventsWaitedFor, 1);
-  ASSERT_EQ(TestContext->EventReferenceCount, 0);
-  Q.wait();
-  ASSERT_EQ(TestContext->NEventsWaitedFor, 1);
-}
-
-// Check that shared events are cleaned up from the queue once their number
-// exceeds a threshold.
-TEST(QueueEventClear, CleanupOnThreshold) {
-  sycl::unittest::UrMock<> Mock;
-  sycl::platform Plt = sycl::platform();
-  prepareUrMock(Mock);
-
-  context Ctx{Plt.get_devices()[0]};
-  TestContext.reset(new TestCtx(Ctx));
-  queue Q{Ctx, default_selector()};
-
-  unsigned char *HostAlloc = (unsigned char *)malloc_host(1, Ctx);
-  TestContext->EventReferenceCount = ExpectedEventThreshold;
-  for (size_t I = 0; I < ExpectedEventThreshold; ++I) {
-    Q.memset(HostAlloc, 42, 1).wait();
-  }
-  // Half of the events (those reported as completed) should be released.
-  Q.memset(HostAlloc, 42, 1);
-  ASSERT_EQ(TestContext->EventReferenceCount, ExpectedEventThreshold / 2);
-}
diff --git a/sycl/unittests/queue/Wait.cpp b/sycl/unittests/queue/Wait.cpp
index 023f07a0a5284..95e32a2ff8a43 100644
--- a/sycl/unittests/queue/Wait.cpp
+++ b/sycl/unittests/queue/Wait.cpp
@@ -138,18 +138,6 @@ TEST(QueueWait, QueueWaitTest) {
     ASSERT_EQ(TestContext.NEventsWaitedFor, 1);
     ASSERT_TRUE(TestContext.UrQueueFinishCalled);
   }
-
-  // Test behaviour for emulating an OOO queue with multiple in-order ones.
-  TestContext = {};
-  TestContext.SupportOOO = false;
-  Q = queue{Ctx, default_selector()};
-  Q.memset(HostAlloc, 42, 1);
-  // The event is kept alive in this case to call wait.
-  ASSERT_EQ(TestContext.EventReferenceCount, 1);
-  Q.wait();
-  ASSERT_EQ(TestContext.EventReferenceCount, 0);
-  ASSERT_EQ(TestContext.NEventsWaitedFor, 1);
-  ASSERT_FALSE(TestContext.UrQueueFinishCalled);
 }
 
 } // namespace


Reviews:
user: login: fabiomestre
body: I will create an internal ticket to look into the graph issue.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T14:50:41Z
user: login: sergey-semenov
body: LGTM overall
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T14:13:12Z
user: login: jbrodman
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T14:41:59Z
user: login: sergey-semenov
body: One more minor comment, missed it the first time.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T15:37:21Z
user: login: jbrodman
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T15:45:12Z
user: login: sergey-semenov
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T16:33:49Z

Review comments:
diff_hunk: @@ -627,27 +580,20 @@ void queue_impl::wait(const detail::code_location &CodeLoc) {
   // directly. Otherwise, only wait for unenqueued or host task events, starting
   // from the latest submitted task in order to minimize total amount of calls,
   // then handle the rest with urQueueFinish.
-  const bool SupportsPiFinish = !MEmulateOOO;
   for (auto EventImplWeakPtrIt = WeakEvents.rbegin();
        EventImplWeakPtrIt != WeakEvents.rend(); ++EventImplWeakPtrIt) {
     if (std::shared_ptr<event_impl> EventImplSharedPtr =
             EventImplWeakPtrIt->lock()) {
       // A nullptr UR event indicates that urQueueFinish will not cover it,
       // either because it's a host task event or an unenqueued one.
-      if (!SupportsPiFinish || nullptr == EventImplSharedPtr->getHandle()) {
+      if (!EventImplSharedPtr->hasCommandGraph() &&
path: sycl/source/detail/queue_impl.cpp
user: login: sergey-semenov
body: Why is the graph check necessary here now?
created_at: 2025-04-11T13:59:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -271,6 +271,7 @@ TEST_F(CommandGraphTest, InOrderQueueWithPreviousHostTask) {
 
   // Record in-order queue with three nodes.
   InOrderGraph.begin_recording(InOrderQueue);
+#if 1
path: sycl/unittests/Extensions/CommandGraph/InOrderQueue.cpp
user: login: sergey-semenov
body: Just a reminder to remove this.
created_at: 2025-04-11T14:12:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -627,27 +580,20 @@ void queue_impl::wait(const detail::code_location &CodeLoc) {
   // directly. Otherwise, only wait for unenqueued or host task events, starting
   // from the latest submitted task in order to minimize total amount of calls,
   // then handle the rest with urQueueFinish.
-  const bool SupportsPiFinish = !MEmulateOOO;
   for (auto EventImplWeakPtrIt = WeakEvents.rbegin();
        EventImplWeakPtrIt != WeakEvents.rend(); ++EventImplWeakPtrIt) {
     if (std::shared_ptr<event_impl> EventImplSharedPtr =
             EventImplWeakPtrIt->lock()) {
       // A nullptr UR event indicates that urQueueFinish will not cover it,
       // either because it's a host task event or an unenqueued one.
-      if (!SupportsPiFinish || nullptr == EventImplSharedPtr->getHandle()) {
+      if (!EventImplSharedPtr->hasCommandGraph() &&
path: sycl/source/detail/queue_impl.cpp
user: login: jbrodman
body: Oops forgot to push the commit that removes it.
created_at: 2025-04-11T14:41:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -542,60 +540,14 @@ class queue_impl {
     }
     ur_result_t Error = Adapter->call_nocheck<UrApiKind::urQueueCreate>(
         Context, Device, &Properties, &Queue);
-
-    // If creating out-of-order queue failed and this property is not
-    // supported (for example, on FPGA), it will return
-    // UR_RESULT_ERROR_INVALID_QUEUE_PROPERTIES and will try to create in-order
-    // queue.
-    if (!MEmulateOOO && Error == UR_RESULT_ERROR_INVALID_QUEUE_PROPERTIES) {
-      MEmulateOOO = true;
-      Queue = createQueue(QueueOrder::Ordered);
-    } else {
-      Adapter->checkUrResult(Error);
-    }
+    Adapter->checkUrResult(Error);
path: sycl/source/detail/queue_impl.hpp
user: login: sergey-semenov
body: L541-L543 are equivalent to just `Adapter->call<...>`
created_at: 2025-04-11T15:35:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -542,60 +540,14 @@ class queue_impl {
     }
     ur_result_t Error = Adapter->call_nocheck<UrApiKind::urQueueCreate>(
         Context, Device, &Properties, &Queue);
-
-    // If creating out-of-order queue failed and this property is not
-    // supported (for example, on FPGA), it will return
-    // UR_RESULT_ERROR_INVALID_QUEUE_PROPERTIES and will try to create in-order
-    // queue.
-    if (!MEmulateOOO && Error == UR_RESULT_ERROR_INVALID_QUEUE_PROPERTIES) {
-      MEmulateOOO = true;
-      Queue = createQueue(QueueOrder::Ordered);
-    } else {
-      Adapter->checkUrResult(Error);
-    }
+    Adapter->checkUrResult(Error);
path: sycl/source/detail/queue_impl.hpp
user: login: jbrodman
body: Good catch. Tweaked this to call the checking version.
created_at: 2025-04-14T15:45:12Z
author_association: CONTRIBUTOR

PR comments:
user: login: jbrodman
created_at: 2025-04-09T20:39:11Z
body: Note that I was seeing an issue in a Graph unit test. Graph recording was doing strange things. I added # if 1 / #endif around the test to easily toggle things on/off for now. If the actual commands during recording are omitted, it was fine. 
author_association: CONTRIBUTOR
user: login: jbrodman
created_at: 2025-04-10T19:26:13Z
body: The test fails were random test timeouts that seem unrelated to these changes.
author_association: CONTRIBUTOR
user: login: jbrodman
created_at: 2025-04-10T19:26:54Z
body: > Note that I was seeing an issue in a Graph unit test. Graph recording was doing strange things. I added # if 1 / #endif around the test to easily toggle things on/off for now. If the actual commands during recording are omitted, it was fine.

Turns out I also nuked 1 if statement too many. That resolves the Graph issue I was seeing.
author_association: CONTRIBUTOR