//
// Modifications, Copyright (C) 2024 Intel Corporation
//
// This software and the related documents are Intel copyrighted materials, and
// your use of them is governed by the express license under which they were
// provided to you ("License"). Unless the License provides otherwise, you may
// not use, modify, copy, publish, distribute, disclose or transmit this
// software or the related documents without Intel's prior written permission.
//
// This software and the related documents are provided as is, with no express
// or implied warranties, other than those that are expressly stated in the
// License.
//
//==-------------------- queue.hpp - SYCL queue ----------------------------==//
//
// 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
//
//===----------------------------------------------------------------------===//

#pragma once

#include <sycl/access/access.hpp>             // for target, access...
#include <sycl/accessor.hpp>                  // for accessor
#include <sycl/aspects.hpp>                   // for aspect
#include <sycl/async_handler.hpp>             // for async_handler
#include <sycl/backend_types.hpp>             // for backend, backe...
#include <sycl/buffer.hpp>                    // for buffer
#include <sycl/detail/cg_types.hpp>           // for check_fn_signa...
#include <sycl/detail/common.hpp>             // for code_location
#include <sycl/detail/defines_elementary.hpp> // for __SYCL2020_DEP...
#include <sycl/detail/export.hpp>             // for __SYCL_EXPORT
#include <sycl/detail/id_queries_fit_in_int.hpp> // for checkValueRange
#include <sycl/detail/info_desc_helpers.hpp>  // for is_queue_info_...
#include <sycl/detail/kernel_desc.hpp>        // for KernelInfo
#include <sycl/detail/nd_range_view.hpp>
#include <sycl/detail/optional.hpp>
#include <sycl/detail/owner_less_base.hpp> // for OwnerLessBase
#include <sycl/detail/range_rounding.hpp>  // for range rounding utils
#include <sycl/device.hpp>                 // for device
#include <sycl/device_selector.hpp>        // for device_selector
#include <sycl/event.hpp>                  // for event
#include <sycl/exception.hpp>              // for make_error_code
#include <sycl/exception_list.hpp>         // for defaultAsyncHa...
#include <sycl/ext/oneapi/device_global/device_global.hpp> // for device_global
#include <sycl/ext/oneapi/device_global/properties.hpp> // for device_image_s...
#include <sycl/ext/oneapi/experimental/event_mode_property.hpp>
#include <sycl/ext/oneapi/experimental/graph.hpp>    // for command_graph...
#include <sycl/ext/oneapi/properties/properties.hpp> // for empty_properti...
#include <sycl/handler.hpp>                          // for handler, isDev...
#include <sycl/id.hpp>                               // for id
#include <sycl/kernel.hpp>                           // for auto_name
#include <sycl/kernel_handler.hpp>                   // for kernel_handler
#include <sycl/nd_range.hpp>                         // for nd_range
#include <sycl/property_list.hpp>                    // for property_list
#include <sycl/range.hpp>                            // for range
#include <sycl/sycl_span.hpp>                        // for sycl::span

#include <cstddef>     // for size_t
#include <functional>  // for function
#include <memory>      // for shared_ptr, hash
#include <stdint.h>    // for int32_t
#include <tuple>       // for tuple
#include <type_traits> // for remove_all_ext...
#include <variant>     // for hash
#include <vector>      // for vector

namespace sycl {
inline namespace _V1 {

// Forward declaration
class context;
class device;
class event;
class queue;

template <backend BackendName, class SyclObjectT>
auto get_native(const SyclObjectT &Obj)
    -> backend_return_t<BackendName, SyclObjectT>;

event __SYCL_EXPORT submit_kernel_direct_with_event_impl(
    const queue &Queue, const detail::nd_range_view &RangeView,
    detail::HostKernelRefBase &HostKernel,
    detail::DeviceKernelInfo *DeviceKernelInfo,
    sycl::span<const event> DepEvents,
    const detail::KernelPropertyHolderStructTy &Props,
    const detail::code_location &CodeLoc, bool IsTopCodeLoc);

void __SYCL_EXPORT submit_kernel_direct_without_event_impl(
    const queue &Queue, const detail::nd_range_view &RangeView,
    detail::HostKernelRefBase &HostKernel,
    detail::DeviceKernelInfo *DeviceKernelInfo,
    sycl::span<const event> DepEvents,
    const detail::KernelPropertyHolderStructTy &Props,
    const detail::code_location &CodeLoc, bool IsTopCodeLoc);
event __SYCL_EXPORT submit_graph_direct_with_event_impl(
    const queue &Queue,
    ext::oneapi::experimental::command_graph<
        ext::oneapi::experimental::graph_state::executable> &G,
    sycl::span<const event> DepEvents,
    const detail::code_location &CodeLoc = detail::code_location::current());

void __SYCL_EXPORT submit_graph_direct_without_event_impl(
    const queue &Queue,
    ext::oneapi::experimental::command_graph<
        ext::oneapi::experimental::graph_state::executable> &G,
    sycl::span<const event> DepEvents,
    const detail::code_location &CodeLoc = detail::code_location::current());

namespace detail {
class queue_impl;

inline namespace _V1 {

// This class is a part of the ABI, so it's moved to a separate namespace to
// simplify changes.
// To perform non-ABI breaking changes:
// * namespace v(N+1) can be added,
// * functions that use SubmissionInfo and are NOT part of the ABI should be
//   switched to use v(N+1) namespace,
// * functions that use SubmissionInfo and are part of the ABI should be
//   overloaded with a new variant using v(N+1) namespace,
// * old namespace vN should be moved under #ifndef
//   __INTEL_PREVIEW_BREAKING_CHANGES guard.
class __SYCL_EXPORT SubmissionInfo {
public:
  SubmissionInfo() {}

  ext::oneapi::experimental::event_mode_enum &EventMode() { return MEventMode; }
  const ext::oneapi::experimental::event_mode_enum &EventMode() const {
    return MEventMode;
  }

private:
  ext::oneapi::experimental::event_mode_enum MEventMode =
      ext::oneapi::experimental::event_mode_enum::none;
};

} // namespace _V1

template <detail::WrapAs WrapAs, typename LambdaArgType, typename KernelName,
          bool EventNeeded = false,
          typename PropertiesT = ext::oneapi::experimental::empty_properties_t,
          typename KernelTypeUniversalRef, int Dims>
auto submit_kernel_direct(
    const queue &Queue, const detail::nd_range_view &RangeView,
    KernelTypeUniversalRef &&KernelFunc, sycl::span<const event> DepEvents,
    const PropertiesT &ExtraProps =
        ext::oneapi::experimental::empty_properties_t{},
    const detail::code_location &CodeLoc = detail::code_location::current());

template <typename KernelName = detail::auto_name, bool EventNeeded = false,
          typename PropertiesT = ext::oneapi::experimental::empty_properties_t,
          typename KernelTypeUniversalRef, int Dims>
auto submit_kernel_direct_parallel_for(
    const queue &Queue, nd_range<Dims> Range,
    KernelTypeUniversalRef &&KernelFunc, sycl::span<const event> DepEvents = {},
    const PropertiesT &Props = ext::oneapi::experimental::empty_properties_t{},
    const detail::code_location &CodeLoc = detail::code_location::current());

template <typename KernelName = detail::auto_name, bool EventNeeded = false,
          typename PropertiesT = ext::oneapi::experimental::empty_properties_t,
          typename KernelTypeUniversalRef, int Dims>
auto submit_kernel_direct_parallel_for(
    const queue &Queue, range<Dims> Range, KernelTypeUniversalRef &&KernelFunc,
    sycl::span<const event> DepEvents = {},
    const PropertiesT &Props = ext::oneapi::experimental::empty_properties_t{},
    const detail::code_location &CodeLoc = detail::code_location::current());

template <typename KernelName = detail::auto_name, bool EventNeeded = false,
          typename PropertiesT = ext::oneapi::experimental::empty_properties_t,
          typename KernelTypeUniversalRef>
auto submit_kernel_direct_single_task(
    const queue &Queue, KernelTypeUniversalRef &&KernelFunc,
    sycl::span<const event> DepEvents = {},
    const PropertiesT &Props = ext::oneapi::experimental::empty_properties_t{},
    const detail::code_location &CodeLoc = detail::code_location::current());

} // namespace detail

namespace ext ::oneapi ::experimental {
// State of a queue with regards to graph recording,
// returned by info::queue::state
enum class queue_state { executing, recording };
struct image_descriptor;

namespace detail {
template <typename CommandGroupFunc, typename PropertiesT>
void submit_impl(const queue &Q, PropertiesT Props, CommandGroupFunc &&CGF,
                 const sycl::detail::code_location &CodeLoc);

template <typename CommandGroupFunc, typename PropertiesT>
event submit_with_event_impl(const queue &Q, PropertiesT Props,
                             CommandGroupFunc &&CGF,
                             const sycl::detail::code_location &CodeLoc);
} // namespace detail
} // namespace ext::oneapi::experimental

/// Encapsulates a single SYCL queue which schedules kernels on a SYCL device.
///
/// A SYCL queue can be used to submit command groups to be executed by the SYCL
/// runtime.
///
/// \sa device
/// \sa handler
/// \sa event
/// \sa kernel
///
/// \ingroup sycl_api
class __SYCL_EXPORT queue : public detail::OwnerLessBase<queue> {
  friend sycl::detail::ImplUtils;

public:
  /// Constructs a SYCL queue instance using the device returned by an instance
  /// of default_selector.
  ///
  /// \param PropList is a list of properties for queue construction.
  explicit queue(const property_list &PropList = {})
      : queue(default_selector_v, detail::defaultAsyncHandler, PropList) {}

  /// Constructs a SYCL queue instance with an async_handler using the device
  /// returned by an instance of default_selector.
  ///
  /// \param AsyncHandler is a SYCL asynchronous exception handler.
  /// \param PropList is a list of properties for queue construction.
  queue(const async_handler &AsyncHandler, const property_list &PropList = {})
      : queue(default_selector_v, AsyncHandler, PropList) {}

  /// Constructs a SYCL queue instance using the device identified by the
  /// device selector provided.
  /// \param DeviceSelector is SYCL 2020 Device Selector, a simple callable that
  /// takes a device and returns an int
  /// \param AsyncHandler is a SYCL asynchronous exception handler.
  /// \param PropList is a list of properties for queue construction.
  template <typename DeviceSelector,
            typename =
                detail::EnableIfSYCL2020DeviceSelectorInvocable<DeviceSelector>>
  explicit queue(const DeviceSelector &deviceSelector,
                 const async_handler &AsyncHandler,
                 const property_list &PropList = {})
      : queue(detail::select_device(deviceSelector), AsyncHandler, PropList) {}

  /// Constructs a SYCL queue instance using the device identified by the
  /// device selector provided.
  /// \param DeviceSelector is SYCL 2020 Device Selector, a simple callable that
  /// takes a device and returns an int
  /// \param PropList is a list of properties for queue construction.
  template <typename DeviceSelector,
            typename =
                detail::EnableIfSYCL2020DeviceSelectorInvocable<DeviceSelector>>
  explicit queue(const DeviceSelector &deviceSelector,
                 const property_list &PropList = {})
      : queue(detail::select_device(deviceSelector),
              detail::defaultAsyncHandler, PropList) {}

  /// Constructs a SYCL queue instance using the device identified by the
  /// device selector provided.
  /// \param SyclContext is an instance of SYCL context.
  /// \param DeviceSelector is SYCL 2020 Device Selector, a simple callable that
  /// takes a device and returns an int
  /// \param PropList is a list of properties for queue construction.
  template <typename DeviceSelector,
            typename =
                detail::EnableIfSYCL2020DeviceSelectorInvocable<DeviceSelector>>
  explicit queue(const context &syclContext,
                 const DeviceSelector &deviceSelector,
                 const property_list &propList = {})
      : queue(syclContext, detail::select_device(deviceSelector, syclContext),
              propList) {}

  /// Constructs a SYCL queue instance using the device identified by the
  /// device selector provided.
  /// \param SyclContext is an instance of SYCL context.
  /// \param DeviceSelector is SYCL 2020 Device Selector, a simple callable that
  /// takes a device and returns an int
  /// \param AsyncHandler is a SYCL asynchronous exception handler.
  /// \param PropList is a list of properties for queue construction.
  template <typename DeviceSelector,
            typename =
                detail::EnableIfSYCL2020DeviceSelectorInvocable<DeviceSelector>>
  explicit queue(const context &syclContext,
                 const DeviceSelector &deviceSelector,
                 const async_handler &AsyncHandler,
                 const property_list &propList = {})
      : queue(syclContext, detail::select_device(deviceSelector, syclContext),
              AsyncHandler, propList) {}

  /// Constructs a SYCL queue instance using the device returned by the
  /// DeviceSelector provided.
  ///
  /// \param DeviceSelector is an instance of a SYCL 1.2.1 device_selector.
  /// \param PropList is a list of properties for queue construction.
  __SYCL2020_DEPRECATED("SYCL 1.2.1 device selectors are deprecated. Please "
                        "use SYCL 2020 device selectors instead.")
  queue(const device_selector &DeviceSelector,
        const property_list &PropList = {})
      : queue(DeviceSelector.select_device(), detail::defaultAsyncHandler,
              PropList) {}

  /// Constructs a SYCL queue instance with an async_handler using the device
  /// returned by the DeviceSelector provided.
  ///
  /// \param DeviceSelector is an instance of SYCL 1.2.1 device_selector.
  /// \param AsyncHandler is a SYCL asynchronous exception handler.
  /// \param PropList is a list of properties for queue construction.
  __SYCL2020_DEPRECATED("SYCL 1.2.1 device selectors are deprecated. Please "
                        "use SYCL 2020 device selectors instead.")
  queue(const device_selector &DeviceSelector,
        const async_handler &AsyncHandler, const property_list &PropList = {})
      : queue(DeviceSelector.select_device(), AsyncHandler, PropList) {}

  /// Constructs a SYCL queue instance using the device provided.
  ///
  /// \param SyclDevice is an instance of SYCL device.
  /// \param PropList is a list of properties for queue construction.
  explicit queue(const device &SyclDevice, const property_list &PropList = {})
      : queue(SyclDevice, detail::defaultAsyncHandler, PropList) {}

  /// Constructs a SYCL queue instance with an async_handler using the device
  /// provided.
  ///
  /// \param SyclDevice is an instance of SYCL device.
  /// \param AsyncHandler is a SYCL asynchronous exception handler.
  /// \param PropList is a list of properties for queue construction.
  explicit queue(const device &SyclDevice, const async_handler &AsyncHandler,
                 const property_list &PropList = {});

  /// Constructs a SYCL queue instance that is associated with the context
  /// provided, using the device returned by the device selector.
  ///
  /// \param SyclContext is an instance of SYCL context.
  /// \param DeviceSelector is an instance of SYCL device selector.
  /// \param PropList is a list of properties for queue construction.
  __SYCL2020_DEPRECATED("SYCL 1.2.1 device selectors are deprecated. Please "
                        "use SYCL 2020 device selectors instead.")
  queue(const context &SyclContext, const device_selector &DeviceSelector,
        const property_list &PropList = {});

  /// Constructs a SYCL queue instance with an async_handler that is associated
  /// with the context provided, using the device returned by the device
  /// selector.
  ///
  /// \param SyclContext is an instance of SYCL context.
  /// \param DeviceSelector is an instance of SYCL device selector.
  /// \param AsyncHandler is a SYCL asynchronous exception handler.
  /// \param PropList is a list of properties for queue construction.
  __SYCL2020_DEPRECATED("SYCL 1.2.1 device selectors are deprecated. Please "
                        "use SYCL 2020 device selectors instead.")
  queue(const context &SyclContext, const device_selector &DeviceSelector,
        const async_handler &AsyncHandler, const property_list &PropList = {});

  /// Constructs a SYCL queue associated with the given context, device
  /// and optional properties list.
  ///
  /// \param SyclContext is an instance of SYCL context.
  /// \param SyclDevice is an instance of SYCL device.
  /// \param PropList is a list of properties for queue construction.
  queue(const context &SyclContext, const device &SyclDevice,
        const property_list &PropList = {});

  /// Constructs a SYCL queue associated with the given context, device,
  /// asynchronous exception handler and optional properties list.
  ///
  /// \param SyclContext is an instance of SYCL context.
  /// \param SyclDevice is an instance of SYCL device.
  /// \param AsyncHandler is a SYCL asynchronous exception handler.
  /// \param PropList is a list of properties for queue construction.
  queue(const context &SyclContext, const device &SyclDevice,
        const async_handler &AsyncHandler, const property_list &PropList = {});

  /// Constructs a SYCL queue with an optional async_handler from an OpenCL
  /// cl_command_queue.
  ///
  /// The instance of cl_command_queue is retained on construction.
  ///
  /// \param ClQueue is a valid instance of OpenCL queue.
  /// \param SyclContext is a valid SYCL context.
  /// \param AsyncHandler is a SYCL asynchronous exception handler.
#ifdef __SYCL_INTERNAL_API
  queue(cl_command_queue ClQueue, const context &SyclContext,
        const async_handler &AsyncHandler = {});
#endif

  queue(const queue &RHS) = default;

  queue(queue &&RHS) = default;

  queue &operator=(const queue &RHS) = default;

  queue &operator=(queue &&RHS) = default;

  bool operator==(const queue &RHS) const { return impl == RHS.impl; }

  bool operator!=(const queue &RHS) const { return !(*this == RHS); }

  /// \return a valid instance of OpenCL queue, which is retained before being
  /// returned.
#ifdef __SYCL_INTERNAL_API
  cl_command_queue get() const;
#endif

  /// \return an associated SYCL context.
  context get_context() const;

  /// \return SYCL device this queue was constructed with.
  device get_device() const;

  /// \return State the queue is currently in.
  ext::oneapi::experimental::queue_state ext_oneapi_get_state() const;

  /// \return Graph when the queue is recording.
  ext::oneapi::experimental::command_graph<
      ext::oneapi::experimental::graph_state::modifiable>
  ext_oneapi_get_graph() const;

  /// Queries SYCL queue for information.
  ///
  /// The return type depends on information being queried.
  template <typename Param>
  typename detail::is_queue_info_desc<Param>::return_type get_info() const;

  /// Queries SYCL queue for SYCL backend-specific information.
  ///
  /// The return type depends on information being queried.
  template <typename Param>
  typename detail::is_backend_info_desc<Param>::return_type
  get_backend_info() const;

public:
  /// Submits a command group function object to the queue, in order to be
  /// scheduled for execution on the device.
  ///
  /// \param CGF is a function object containing command group.
  /// \param CodeLoc is the code location of the submit call (default argument)
  /// \return a SYCL event object for the submitted command group.
  template <typename T>
  std::enable_if_t<std::is_invocable_r_v<void, T, handler &>, event> submit(
      T CGF,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return submit_with_event(
        sycl::ext::oneapi::experimental::empty_properties_t{},
        detail::type_erased_cgfo_ty{CGF}, CodeLoc);
  }

  /// Submits a command group function object to the queue, in order to be
  /// scheduled for execution on the device.
  ///
  /// \param CGF is a function object containing command group.
  /// \param SecondaryQueue is a fallback SYCL queue. (unused)
  /// \param CodeLoc is the code location of the submit call (default argument)
  /// \return a SYCL event object, which corresponds to the queue the command
  /// group is being enqueued on.
  template <typename T>
  std::enable_if_t<std::is_invocable_r_v<void, T, handler &>, event> submit(
      T CGF, [[maybe_unused]] queue &SecondaryQueue,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return submit_with_event(
        sycl::ext::oneapi::experimental::empty_properties_t{},
        detail::type_erased_cgfo_ty{CGF}, CodeLoc);
  }

  /// Prevents any commands submitted afterward to this queue from executing
  /// until all commands previously submitted to this queue have entered the
  /// complete state.
  ///
  /// \param CodeLoc is the code location of the submit call (default argument)
  /// \return a SYCL event object, which corresponds to the queue the command
  /// group is being enqueued on.
  event ext_oneapi_submit_barrier(
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Prevents any commands submitted afterward to this queue from executing
  /// until all events in WaitList have entered the complete state. If WaitList
  /// is empty, then ext_oneapi_submit_barrier has no effect.
  ///
  /// \param WaitList is a vector of valid SYCL events that need to complete
  /// before barrier command can be executed.
  /// \param CodeLoc is the code location of the submit call (default argument)
  /// \return a SYCL event object, which corresponds to the queue the command
  /// group is being enqueued on.
  event ext_oneapi_submit_barrier(
      const std::vector<event> &WaitList,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Performs a blocking wait for the completion of all enqueued tasks in the
  /// queue.
  ///
  /// Synchronous errors will be reported through SYCL exceptions.
  /// @param CodeLoc is the code location of the submit call (default argument)
  void wait(
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    wait_proxy(TlsCodeLocCapture.query());
  }

  /// Performs a blocking wait for the completion of all enqueued tasks in the
  /// queue.
  ///
  /// Synchronous errors will be reported through SYCL exceptions. Asynchronous
  /// errors will be passed to the async_handler passed to the queue on
  /// construction. If no async_handler was provided then asynchronous
  /// exceptions will be lost.
  /// @param CodeLoc is the code location of the submit call (default argument)
  void wait_and_throw(
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    wait_and_throw_proxy(TlsCodeLocCapture.query());
  }

  /// Proxy method for wait to forward the code location information to the
  /// implementation
  void wait_proxy(const detail::code_location &CodeLoc);
  /// Proxy method for wait_and_throw to forward the code location information
  /// to the implementation
  void wait_and_throw_proxy(const detail::code_location &CodeLoc);

  /// Checks if any asynchronous errors have been produced by the queue and if
  /// so reports them to the async_handler passed on the queue construction.
  ///
  /// If no async_handler was provided then asynchronous exceptions will be
  /// lost.
  void throw_asynchronous();

  /// \return true if the queue was constructed with property specified by
  /// PropertyT.
  template <typename PropertyT> bool has_property() const noexcept {
    return getPropList().template has_property<PropertyT>();
  }

  /// \return a copy of the property of type PropertyT that the queue was
  /// constructed with. If the queue was not constructed with the PropertyT
  /// property, an SYCL exception with errc::invalid error code is thrown.
  template <typename PropertyT> PropertyT get_property() const {
    return getPropList().template get_property<PropertyT>();
  }

  /// Fills the specified memory with the specified pattern.
  ///
  /// \param Ptr is the pointer to the memory to fill.
  /// \param Pattern is the pattern to fill into the memory.  T should be
  /// trivially copyable.
  /// \param Count is the number of times to fill Pattern into Ptr.
  /// \return an event representing fill operation.
  template <typename T>
  event fill(
      void *Ptr, const T &Pattern, size_t Count,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return submit([&](handler &CGH) { CGH.fill<T>(Ptr, Pattern, Count); },
                  TlsCodeLocCapture.query());
  }

  /// Fills the specified memory with the specified pattern.
  ///
  /// \param Ptr is the pointer to the memory to fill.
  /// \param Pattern is the pattern to fill into the memory.  T should be
  /// trivially copyable.
  /// \param Count is the number of times to fill Pattern into Ptr.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing fill operation.
  template <typename T>
  event fill(
      void *Ptr, const T &Pattern, size_t Count, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return submit(
        [&](handler &CGH) {
          CGH.depends_on(DepEvent);
          CGH.fill<T>(Ptr, Pattern, Count);
        },
        TlsCodeLocCapture.query());
  }

  /// Fills the specified memory with the specified pattern.
  ///
  /// \param Ptr is the pointer to the memory to fill.
  /// \param Pattern is the pattern to fill into the memory.  T should be
  /// trivially copyable.
  /// \param Count is the number of times to fill Pattern into Ptr.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing fill operation.
  template <typename T>
  event fill(
      void *Ptr, const T &Pattern, size_t Count,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return submit(
        [&](handler &CGH) {
          CGH.depends_on(DepEvents);
          CGH.fill<T>(Ptr, Pattern, Count);
        },
        TlsCodeLocCapture.query());
  }

  /// Fills the memory pointed by a USM pointer with the value specified.
  /// No operations is done if \param Count is zero. An exception is thrown
  /// if \param Ptr is nullptr. The behavior is undefined if \param Ptr
  /// is invalid.
  ///
  /// \param Ptr is a USM pointer to the memory to fill.
  /// \param Value is a value to be set. Value is cast as an unsigned char.
  /// \param Count is a number of bytes to fill.
  /// \return an event representing fill operation.
  event memset(
      void *Ptr, int Value, size_t Count,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Fills the memory pointed by a USM pointer with the value specified.
  /// No operations is done if \param Count is zero. An exception is thrown
  /// if \param Ptr is nullptr. The behavior is undefined if \param Ptr
  /// is invalid.
  ///
  /// \param Ptr is a USM pointer to the memory to fill.
  /// \param Value is a value to be set. Value is cast as an unsigned char.
  /// \param Count is a number of bytes to fill.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing fill operation.
  event memset(
      void *Ptr, int Value, size_t Count, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Fills the memory pointed by a USM pointer with the value specified.
  /// No operations is done if \param Count is zero. An exception is thrown
  /// if \param Ptr is nullptr. The behavior is undefined if \param Ptr
  /// is invalid.
  ///
  /// \param Ptr is a USM pointer to the memory to fill.
  /// \param Value is a value to be set. Value is cast as an unsigned char.
  /// \param Count is a number of bytes to fill.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing fill operation.
  event memset(
      void *Ptr, int Value, size_t Count, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from one memory region to another, each is either a host
  /// pointer or a pointer within USM allocation accessible on the device
  /// associated with this queue.
  /// No operations is done if \param Count is zero. An exception is thrown
  /// if either \param Dest or \param Src is nullptr. The behavior is undefined
  /// if any of the pointer parameters is invalid.
  ///
  /// \param Dest is a USM pointer to the destination memory.
  /// \param Src is a USM pointer to the source memory.
  /// \param Count is a number of bytes to copy.
  /// \return an event representing copy operation.
  event memcpy(
      void *Dest, const void *Src, size_t Count,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from one memory region to another, each is either a host
  /// pointer or a pointer within USM allocation accessible on the device
  /// associated with this queue.
  /// No operations is done if \param Count is zero. An exception is thrown
  /// if either \param Dest or \param Src is nullptr. The behavior is undefined
  /// if any of the pointer parameters is invalid.
  ///
  /// \param Dest is a USM pointer to the destination memory.
  /// \param Src is a USM pointer to the source memory.
  /// \param Count is a number of bytes to copy.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing copy operation.
  event memcpy(
      void *Dest, const void *Src, size_t Count, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from one memory region to another, each is either a host
  /// pointer or a pointer within USM allocation accessible on the device
  /// associated with this queue.
  /// No operations is done if \param Count is zero. An exception is thrown
  /// if either \param Dest or \param Src is nullptr. The behavior is undefined
  /// if any of the pointer parameters is invalid.
  ///
  /// \param Dest is a USM pointer to the destination memory.
  /// \param Src is a USM pointer to the source memory.
  /// \param Count is a number of bytes to copy.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing copy operation.
  event memcpy(
      void *Dest, const void *Src, size_t Count,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from one memory region to another, each is either a host
  /// pointer or a pointer within USM allocation accessible on the device
  /// associated with this queue.
  /// No operations is done if \param Count is zero. An exception is thrown
  /// if either \param Dest or \param Src is nullptr. The behavior is undefined
  /// if any of the pointer parameters is invalid.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param Count is a number of elements of type T to copy.
  /// \param CodeLoc contains the code location of user code
  /// \return an event representing copy operation.
  template <typename T>
  event copy(
      const T *Src, T *Dest, size_t Count,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return this->memcpy(Dest, Src, Count * sizeof(T));
  }

  /// Copies data from one memory region to another, each is either a host
  /// pointer or a pointer within USM allocation accessible on the device
  /// associated with this queue.
  /// No operations is done if \param Count is zero. An exception is thrown
  /// if either \param Dest or \param Src is nullptr. The behavior is undefined
  /// if any of the pointer parameters is invalid.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param Count is a number of elements of type T to copy.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \param CodeLoc contains the code location of user code
  /// \return an event representing copy operation.
  template <typename T>
  event copy(
      const T *Src, T *Dest, size_t Count, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return this->memcpy(Dest, Src, Count * sizeof(T), DepEvent);
  }

  /// Copies data from one memory region to another, each is either a host
  /// pointer or a pointer within USM allocation accessible on the device
  /// associated with this queue.
  /// No operations is done if \param Count is zero. An exception is thrown
  /// if either \param Dest or \param Src is nullptr. The behavior is undefined
  /// if any of the pointer parameters is invalid.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param Count is a number of elements of type T to copy.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// \param CodeLoc contains the code location of user code
  /// \return an event representing copy operation.
  template <typename T>
  event copy(
      const T *Src, T *Dest, size_t Count, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return this->memcpy(Dest, Src, Count * sizeof(T), DepEvents);
  }

  /// Provides additional information to the underlying runtime about how
  /// different allocations are used.
  ///
  /// \param Ptr is a USM pointer to the allocation.
  /// \param Length is a number of bytes in the allocation.
  /// \param Advice is a device-defined advice for the specified allocation.
  /// \return an event representing advice operation.
  event mem_advise(
      const void *Ptr, size_t Length, int Advice,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Provides additional information to the underlying runtime about how
  /// different allocations are used.
  ///
  /// \param Ptr is a USM pointer to the allocation.
  /// \param Length is a number of bytes in the allocation.
  /// \param Advice is a device-defined advice for the specified allocation.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing advice operation.
  event mem_advise(
      const void *Ptr, size_t Length, int Advice, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Provides additional information to the underlying runtime about how
  /// different allocations are used.
  ///
  /// \param Ptr is a USM pointer to the allocation.
  /// \param Length is a number of bytes in the allocation.
  /// \param Advice is a device-defined advice for the specified allocation.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing advice operation.
  event mem_advise(
      const void *Ptr, size_t Length, int Advice,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Provides hints to the runtime library that data should be made available
  /// on a device earlier than Unified Shared Memory would normally require it
  /// to be available.
  ///
  /// \param Ptr is a USM pointer to the memory to be prefetched to the device.
  /// \param Count is a number of bytes to be prefetched.
  /// \return an event representing prefetch operation.
  event prefetch(
      const void *Ptr, size_t Count,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return submit([=](handler &CGH) { CGH.prefetch(Ptr, Count); },
                  TlsCodeLocCapture.query());
  }

  /// Provides hints to the runtime library that data should be made available
  /// on a device earlier than Unified Shared Memory would normally require it
  /// to be available.
  ///
  /// \param Ptr is a USM pointer to the memory to be prefetched to the device.
  /// \param Count is a number of bytes to be prefetched.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing prefetch operation.
  event prefetch(
      const void *Ptr, size_t Count, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return submit(
        [=](handler &CGH) {
          CGH.depends_on(DepEvent);
          CGH.prefetch(Ptr, Count);
        },
        TlsCodeLocCapture.query());
  }

  /// Provides hints to the runtime library that data should be made available
  /// on a device earlier than Unified Shared Memory would normally require it
  /// to be available.
  ///
  /// \param Ptr is a USM pointer to the memory to be prefetched to the device.
  /// \param Count is a number of bytes to be prefetched.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing prefetch operation.
  event prefetch(
      const void *Ptr, size_t Count, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return submit(
        [=](handler &CGH) {
          CGH.depends_on(DepEvents);
          CGH.prefetch(Ptr, Count);
        },
        TlsCodeLocCapture.query());
  }

  /// Copies data from one 2D memory region to another, both pointed by
  /// USM pointers.
  /// No operations is done if \param Width or \param Height is zero. An
  /// exception is thrown if either \param Dest or \param Src is nullptr or if
  /// \param Width is strictly greater than either \param DestPitch or
  /// \param SrcPitch. The behavior is undefined if any of the pointer
  /// parameters is invalid.
  ///
  /// NOTE: Function is dependent to prevent the fallback kernels from
  /// materializing without the use of the function.
  ///
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestPitch is the pitch of the rows in \param Dest.
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcPitch is the pitch of the rows in \param Src.
  /// \param Width is the width in bytes of the 2D region to copy.
  /// \param Height is the height in number of rows of the 2D region to copy.
  /// \return an event representing the copy operation.
  template <typename T = unsigned char,
            typename = std::enable_if_t<std::is_same_v<T, unsigned char>>>
  event ext_oneapi_memcpy2d(
      void *Dest, size_t DestPitch, const void *Src, size_t SrcPitch,
      size_t Width, size_t Height,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return submit(
        [=](handler &CGH) {
          CGH.ext_oneapi_memcpy2d<T>(Dest, DestPitch, Src, SrcPitch, Width,
                                     Height);
        },
        CodeLoc);
  }

  /// Copies data from one 2D memory region to another, both pointed by
  /// USM pointers.
  /// No operations is done if \param Width or \param Height is zero. An
  /// exception is thrown if either \param Dest or \param Src is nullptr or if
  /// \param Width is strictly greater than either \param DestPitch or
  /// \param SrcPitch. The behavior is undefined if any of the pointer
  /// parameters is invalid.
  ///
  /// NOTE: Function is dependent to prevent the fallback kernels from
  /// materializing without the use of the function.
  ///
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestPitch is the pitch of the rows in \param Dest.
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcPitch is the pitch of the rows in \param Src.
  /// \param Width is the width in bytes of the 2D region to copy.
  /// \param Height is the height in number of rows of the 2D region to copy.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  template <typename T = unsigned char,
            typename = std::enable_if_t<std::is_same_v<T, unsigned char>>>
  event ext_oneapi_memcpy2d(
      void *Dest, size_t DestPitch, const void *Src, size_t SrcPitch,
      size_t Width, size_t Height, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from one 2D memory region to another, both pointed by
  /// USM pointers.
  /// No operations is done if \param Width or \param Height is zero. An
  /// exception is thrown if either \param Dest or \param Src is nullptr or if
  /// \param Width is strictly greater than either \param DestPitch or
  /// \param SrcPitch. The behavior is undefined if any of the pointer
  /// parameters is invalid.
  ///
  /// NOTE: Function is dependent to prevent the fallback kernels from
  /// materializing without the use of the function.
  ///
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestPitch is the pitch of the rows in \param Dest.
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcPitch is the pitch of the rows in \param Src.
  /// \param Width is the width in bytes of the 2D region to copy.
  /// \param Height is the height in number of rows of the 2D region to copy.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing the copy operation.
  template <typename T = unsigned char,
            typename = std::enable_if_t<std::is_same_v<T, unsigned char>>>
  event ext_oneapi_memcpy2d(
      void *Dest, size_t DestPitch, const void *Src, size_t SrcPitch,
      size_t Width, size_t Height, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from one 2D memory region to another, both pointed by
  /// USM pointers.
  /// No operations is done if \param Width or \param Height is zero. An
  /// exception is thrown if either \param Dest or \param Src is nullptr or if
  /// \param Width is strictly greater than either \param DestPitch or
  /// \param SrcPitch. The behavior is undefined if any of the pointer
  /// parameters is invalid.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcPitch is the pitch of the rows in \param Src.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestPitch is the pitch of the rows in \param Dest.
  /// \param Width is the width in number of elements of the 2D region to copy.
  /// \param Height is the height in number of rows of the 2D region to copy.
  /// \return an event representing the copy operation.
  template <typename T>
  event ext_oneapi_copy2d(
      const T *Src, size_t SrcPitch, T *Dest, size_t DestPitch, size_t Width,
      size_t Height,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from one 2D memory region to another, both pointed by
  /// USM pointers.
  /// No operations is done if \param Width or \param Height is zero. An
  /// exception is thrown if either \param Dest or \param Src is nullptr or if
  /// \param Width is strictly greater than either \param DestPitch or
  /// \param SrcPitch. The behavior is undefined if any of the pointer
  /// parameters is invalid.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcPitch is the pitch of the rows in \param Src.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestPitch is the pitch of the rows in \param Dest.
  /// \param Width is the width in number of elements of the 2D region to copy.
  /// \param Height is the height in number of rows of the 2D region to copy.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  template <typename T>
  event ext_oneapi_copy2d(
      const T *Src, size_t SrcPitch, T *Dest, size_t DestPitch, size_t Width,
      size_t Height, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from one 2D memory region to another, both pointed by
  /// USM pointers.
  /// No operations is done if \param Width or \param Height is zero. An
  /// exception is thrown if either \param Dest or \param Src is nullptr or if
  /// \param Width is strictly greater than either \param DestPitch or
  /// \param SrcPitch. The behavior is undefined if any of the pointer
  /// parameters is invalid.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcPitch is the pitch of the rows in \param Src.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestPitch is the pitch of the rows in \param Dest.
  /// \param Width is the width in number of elements of the 2D region to copy.
  /// \param Height is the height in number of rows of the 2D region to copy.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing the copy operation.
  template <typename T>
  event ext_oneapi_copy2d(
      const T *Src, size_t SrcPitch, T *Dest, size_t DestPitch, size_t Width,
      size_t Height, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Fills the memory pointed by a USM pointer with the value specified.
  /// No operations is done if \param Width or \param Height is zero. An
  /// exception is thrown if either \param Dest or \param Src is nullptr or if
  /// \param Width is strictly greater than \param DestPitch. The behavior is
  /// undefined if any of the pointer parameters is invalid.
  ///
  /// NOTE: Function is dependent to prevent the fallback kernels from
  /// materializing without the use of the function.
  ///
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestPitch is the pitch of the rows in \param Dest.
  /// \param Value is the value to fill into the region in \param Dest. Value is
  /// cast as an unsigned char.
  /// \param Width is the width in number of elements of the 2D region to fill.
  /// \param Height is the height in number of rows of the 2D region to fill.
  /// \return an event representing the fill operation.
  template <typename T = unsigned char,
            typename = std::enable_if_t<std::is_same_v<T, unsigned char>>>
  event ext_oneapi_memset2d(
      void *Dest, size_t DestPitch, int Value, size_t Width, size_t Height,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Fills the memory pointed by a USM pointer with the value specified.
  /// No operations is done if \param Width or \param Height is zero. An
  /// exception is thrown if either \param Dest or \param Src is nullptr or if
  /// \param Width is strictly greater than \param DestPitch. The behavior is
  /// undefined if any of the pointer parameters is invalid.
  ///
  /// NOTE: Function is dependent to prevent the fallback kernels from
  /// materializing without the use of the function.
  ///
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestPitch is the pitch of the rows in \param Dest.
  /// \param Value is the value to fill into the region in \param Dest. Value is
  /// cast as an unsigned char.
  /// \param Width is the width in number of elements of the 2D region to fill.
  /// \param Height is the height in number of rows of the 2D region to fill.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the fill operation.
  template <typename T = unsigned char,
            typename = std::enable_if_t<std::is_same_v<T, unsigned char>>>
  event ext_oneapi_memset2d(
      void *Dest, size_t DestPitch, int Value, size_t Width, size_t Height,
      event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Fills the memory pointed by a USM pointer with the value specified.
  /// No operations is done if \param Width or \param Height is zero. An
  /// exception is thrown if either \param Dest or \param Src is nullptr or if
  /// \param Width is strictly greater than \param DestPitch. The behavior is
  /// undefined if any of the pointer parameters is invalid.
  ///
  /// NOTE: Function is dependent to prevent the fallback kernels from
  /// materializing without the use of the function.
  ///
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestPitch is the pitch of the rows in \param Dest.
  /// \param Value is the value to fill into the region in \param Dest. Value is
  /// cast as an unsigned char.
  /// \param Width is the width in number of elements of the 2D region to fill.
  /// \param Height is the height in number of rows of the 2D region to fill.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing the fill operation.
  template <typename T = unsigned char,
            typename = std::enable_if_t<std::is_same_v<T, unsigned char>>>
  event ext_oneapi_memset2d(
      void *Dest, size_t DestPitch, int Value, size_t Width, size_t Height,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Fills the memory pointed by a USM pointer with the value specified.
  /// No operations is done if \param Width or \param Height is zero. An
  /// exception is thrown if either \param Dest or \param Src is nullptr or if
  /// \param Width is strictly greater than \param DestPitch. The behavior is
  /// undefined if any of the pointer parameters is invalid.
  ///
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestPitch is the pitch of the rows in \param Dest.
  /// \param Pattern is the pattern to fill into the memory.  T should be
  /// trivially copyable.
  /// \param Width is the width in number of elements of the 2D region to fill.
  /// \param Height is the height in number of rows of the 2D region to fill.
  /// \return an event representing the fill operation.
  template <typename T>
  event ext_oneapi_fill2d(
      void *Dest, size_t DestPitch, const T &Pattern, size_t Width,
      size_t Height,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Fills the memory pointed by a USM pointer with the value specified.
  /// No operations is done if \param Width or \param Height is zero. An
  /// exception is thrown if either \param Dest or \param Src is nullptr or if
  /// \param Width is strictly greater than \param DestPitch. The behavior is
  /// undefined if any of the pointer parameters is invalid.
  ///
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestPitch is the pitch of the rows in \param Dest.
  /// \param Pattern is the pattern to fill into the memory.  T should be
  /// trivially copyable.
  /// \param Width is the width in number of elements of the 2D region to fill.
  /// \param Height is the height in number of rows of the 2D region to fill.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the fill operation.
  template <typename T>
  event ext_oneapi_fill2d(
      void *Dest, size_t DestPitch, const T &Pattern, size_t Width,
      size_t Height, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Fills the memory pointed by a USM pointer with the value specified.
  /// No operations is done if \param Width or \param Height is zero. An
  /// exception is thrown if either \param Dest or \param Src is nullptr or if
  /// \param Width is strictly greater than \param DestPitch. The behavior is
  /// undefined if any of the pointer parameters is invalid.
  ///
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestPitch is the pitch of the rows in \param Dest.
  /// \param Pattern is the pattern to fill into the memory.  T should be
  /// trivially copyable.
  /// \param Width is the width in number of elements of the 2D region to fill.
  /// \param Height is the height in number of rows of the 2D region to fill.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing the fill operation.
  template <typename T>
  event ext_oneapi_fill2d(
      void *Dest, size_t DestPitch, const T &Pattern, size_t Width,
      size_t Height, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from a USM memory region to a device_global.
  /// Throws an exception if the copy operation intends to write outside the
  /// memory range \param Dest, as specified through \param NumBytes and
  /// \param Offset.
  ///
  /// \param Dest is the destination device_glboal.
  /// \param Src is a USM pointer to the source memory.
  /// \param NumBytes is a number of bytes to copy.
  /// \param Offset is the offset into \param Dest to copy to.
  /// \param DepEvents is a vector of events that specifies the operation
  /// dependencies.
  /// \return an event representing copy operation.
  template <typename T, typename PropertyListT>
  event memcpy(
      ext::oneapi::experimental::device_global<T, PropertyListT> &Dest,
      const void *Src, size_t NumBytes, size_t Offset,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    if (sizeof(T) < Offset + NumBytes)
      throw sycl::exception(make_error_code(errc::invalid),
                            "Copy to device_global is out of bounds.");

    if (!detail::isDeviceGlobalUsedInKernel(&Dest)) {
      // device_global is unregistered so we need a fallback. We let the handler
      // implement this fallback.
      return submit(
          [&](handler &CGH) {
            CGH.depends_on(DepEvents);
            return CGH.memcpy(Dest, Src, NumBytes, Offset);
          },
          TlsCodeLocCapture.query());
    }

    constexpr bool IsDeviceImageScoped = PropertyListT::template has_property<
        ext::oneapi::experimental::device_image_scope_key>();
    return memcpyToDeviceGlobal(&Dest, Src, IsDeviceImageScoped, NumBytes,
                                Offset, DepEvents);
  }

  /// Copies data from a USM memory region to a device_global.
  /// Throws an exception if the copy operation intends to write outside the
  /// memory range \param Dest, as specified through \param NumBytes and
  /// \param Offset.
  ///
  /// \param Dest is the destination device_glboal.
  /// \param Src is a USM pointer to the source memory.
  /// \param NumBytes is a number of bytes to copy.
  /// \param Offset is the offset into \param Dest to copy to.
  /// \param DepEvent is a vector of event that specifies the operation
  /// dependency.
  /// \return an event representing copy operation.
  template <typename T, typename PropertyListT>
  event memcpy(
      ext::oneapi::experimental::device_global<T, PropertyListT> &Dest,
      const void *Src, size_t NumBytes, size_t Offset, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return this->memcpy(Dest, Src, NumBytes, Offset,
                        std::vector<event>{DepEvent});
  }

  /// Copies data from a USM memory region to a device_global.
  /// Throws an exception if the copy operation intends to write outside the
  /// memory range \param Dest, as specified through \param NumBytes and
  /// \param Offset.
  ///
  /// \param Dest is the destination device_glboal.
  /// \param Src is a USM pointer to the source memory.
  /// \param NumBytes is a number of bytes to copy.
  /// \param Offset is the offset into \param Dest to copy to.
  /// \return an event representing copy operation.
  template <typename T, typename PropertyListT>
  event memcpy(
      ext::oneapi::experimental::device_global<T, PropertyListT> &Dest,
      const void *Src, size_t NumBytes = sizeof(T), size_t Offset = 0,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return this->memcpy(Dest, Src, NumBytes, Offset, std::vector<event>{});
  }

  /// Copies data from a device_global to USM memory.
  /// Throws an exception if the copy operation intends to read outside the
  /// memory range \param Src, as specified through \param NumBytes and
  /// \param Offset.
  ///
  /// \param Dest is a USM pointer to copy to.
  /// \param Src is the source device_global.
  /// \param NumBytes is a number of bytes to copy.
  /// \param Offset is the offset into \param Src to copy from.
  /// \param DepEvents is a vector of events that specifies the operation
  /// dependencies.
  /// \return an event representing copy operation.
  template <typename T, typename PropertyListT>
  event memcpy(
      void *Dest,
      const ext::oneapi::experimental::device_global<T, PropertyListT> &Src,
      size_t NumBytes, size_t Offset, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    if (sizeof(T) < Offset + NumBytes)
      throw sycl::exception(make_error_code(errc::invalid),
                            "Copy from device_global is out of bounds.");

    if (!detail::isDeviceGlobalUsedInKernel(&Src)) {
      // device_global is unregistered so we need a fallback. We let the handler
      // implement this fallback.
      return submit([&](handler &CGH) {
        CGH.depends_on(DepEvents);
        return CGH.memcpy(Dest, Src, NumBytes, Offset);
      });
    }

    constexpr bool IsDeviceImageScoped = PropertyListT::template has_property<
        ext::oneapi::experimental::device_image_scope_key>();
    return memcpyFromDeviceGlobal(Dest, &Src, IsDeviceImageScoped, NumBytes,
                                  Offset, DepEvents);
  }

  /// Copies data from a device_global to USM memory.
  /// Throws an exception if the copy operation intends to read outside the
  /// memory range \param Src, as specified through \param NumBytes and
  /// \param Offset.
  ///
  /// \param Dest is a USM pointer to copy to.
  /// \param Src is the source device_global.
  /// \param NumBytes is a number of bytes to copy.
  /// \param Offset is the offset into \param Src to copy from.
  /// \param DepEvent is a vector of event that specifies the operation
  /// dependency.
  /// \return an event representing copy operation.
  template <typename T, typename PropertyListT>
  event memcpy(
      void *Dest,
      const ext::oneapi::experimental::device_global<T, PropertyListT> &Src,
      size_t NumBytes, size_t Offset, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return this->memcpy(Dest, Src, NumBytes, Offset,
                        std::vector<event>{DepEvent});
  }

  /// Copies data from a device_global to USM memory.
  /// Throws an exception if the copy operation intends to read outside the
  /// memory range \param Src, as specified through \param NumBytes and
  /// \param Offset.
  ///
  /// \param Dest is a USM pointer to copy to.
  /// \param Src is the source device_global.
  /// \param NumBytes is a number of bytes to copy.
  /// \param Offset is the offset into \param Src to copy from.
  /// \return an event representing copy operation.
  template <typename T, typename PropertyListT>
  event memcpy(
      void *Dest,
      const ext::oneapi::experimental::device_global<T, PropertyListT> &Src,
      size_t NumBytes = sizeof(T), size_t Offset = 0,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return this->memcpy(Dest, Src, NumBytes, Offset, std::vector<event>{});
  }

  /// Copies elements of type `std::remove_all_extents_t<T>` from a USM memory
  /// region to a device_global.
  /// Throws an exception if the copy operation intends to write outside the
  /// memory range \param Dest, as specified through \param Count and
  /// \param StartIndex.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param Dest is the destination device_glboal.
  /// \param Count is a number of elements to copy.
  /// \param StartIndex is the index of the first element in Dest to copy to.
  /// \param DepEvents is a vector of events that specifies the operation
  /// dependencies.
  /// \return an event representing copy operation.
  template <typename T, typename PropertyListT>
  event copy(
      const std::remove_all_extents_t<T> *Src,
      ext::oneapi::experimental::device_global<T, PropertyListT> &Dest,
      size_t Count, size_t StartIndex, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return this->memcpy(Dest, Src, Count * sizeof(std::remove_all_extents_t<T>),
                        StartIndex * sizeof(std::remove_all_extents_t<T>),
                        DepEvents);
  }

  /// Copies elements of type `std::remove_all_extents_t<T>` from a USM memory
  /// region to a device_global.
  /// Throws an exception if the copy operation intends to write outside the
  /// memory range \param Dest, as specified through \param Count and
  /// \param StartIndex.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param Dest is the destination device_glboal.
  /// \param Count is a number of elements to copy.
  /// \param StartIndex is the index of the first element in Dest to copy to.
  /// \param DepEvent is a vector of event that specifies the operation
  /// dependency.
  /// \return an event representing copy operation.
  template <typename T, typename PropertyListT>
  event copy(
      const std::remove_all_extents_t<T> *Src,
      ext::oneapi::experimental::device_global<T, PropertyListT> &Dest,
      size_t Count, size_t StartIndex, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return this->memcpy(Dest, Src, Count * sizeof(std::remove_all_extents_t<T>),
                        StartIndex * sizeof(std::remove_all_extents_t<T>),
                        DepEvent);
  }

  /// Copies elements of type `std::remove_all_extents_t<T>` from a USM memory
  /// region to a device_global.
  /// Throws an exception if the copy operation intends to write outside the
  /// memory range \param Dest, as specified through \param Count and
  /// \param StartIndex.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param Dest is the destination device_glboal.
  /// \param Count is a number of elements to copy.
  /// \param StartIndex is the index of the first element in Dest to copy to.
  /// \return an event representing copy operation.
  template <typename T, typename PropertyListT>
  event copy(
      const std::remove_all_extents_t<T> *Src,
      ext::oneapi::experimental::device_global<T, PropertyListT> &Dest,
      size_t Count = sizeof(T) / sizeof(std::remove_all_extents_t<T>),
      size_t StartIndex = 0,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return this->memcpy(Dest, Src, Count * sizeof(std::remove_all_extents_t<T>),
                        StartIndex * sizeof(std::remove_all_extents_t<T>));
  }

  /// Copies elements of type `std::remove_all_extents_t<T>` from a
  /// device_global to a USM memory region.
  /// Throws an exception if the copy operation intends to write outside the
  /// memory range \param Src, as specified through \param Count and
  /// \param StartIndex.
  ///
  /// \param Src is the source device_global.
  /// \param Dest is a USM pointer to copy to.
  /// \param Count is a number of elements to copy.
  /// \param StartIndex is the index of the first element in Src to copy from.
  /// \param DepEvents is a vector of events that specifies the operation
  /// dependencies.
  /// \return an event representing copy operation.
  template <typename T, typename PropertyListT>
  event copy(
      const ext::oneapi::experimental::device_global<T, PropertyListT> &Src,
      std::remove_all_extents_t<T> *Dest, size_t Count, size_t StartIndex,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return this->memcpy(Dest, Src, Count * sizeof(std::remove_all_extents_t<T>),
                        StartIndex * sizeof(std::remove_all_extents_t<T>),
                        DepEvents);
  }

  /// Copies elements of type `std::remove_all_extents_t<T>` from a
  /// device_global to a USM memory region.
  /// Throws an exception if the copy operation intends to write outside the
  /// memory range \param Src, as specified through \param Count and
  /// \param StartIndex.
  ///
  /// \param Src is the source device_global.
  /// \param Dest is a USM pointer to copy to.
  /// \param Count is a number of elements to copy.
  /// \param StartIndex is the index of the first element in Src to copy from.
  /// \param DepEvent is a vector of event that specifies the operation
  /// dependency.
  /// \return an event representing copy operation.
  template <typename T, typename PropertyListT>
  event copy(
      const ext::oneapi::experimental::device_global<T, PropertyListT> &Src,
      std::remove_all_extents_t<T> *Dest, size_t Count, size_t StartIndex,
      event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return this->memcpy(Dest, Src, Count * sizeof(std::remove_all_extents_t<T>),
                        StartIndex * sizeof(std::remove_all_extents_t<T>),
                        DepEvent);
  }

  /// Copies elements of type `std::remove_all_extents_t<T>` from a
  /// device_global to a USM memory region.
  /// Throws an exception if the copy operation intends to write outside the
  /// memory range \param Src, as specified through \param Count and
  /// \param StartIndex.
  ///
  /// \param Src is the source device_global.
  /// \param Dest is a USM pointer to copy to.
  /// \param Count is a number of elements to copy.
  /// \param StartIndex is the index of the first element in Src to copy from.
  /// \return an event representing copy operation.
  template <typename T, typename PropertyListT>
  event copy(
      const ext::oneapi::experimental::device_global<T, PropertyListT> &Src,
      std::remove_all_extents_t<T> *Dest,
      size_t Count = sizeof(T) / sizeof(std::remove_all_extents_t<T>),
      size_t StartIndex = 0,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return this->memcpy(Dest, Src, Count * sizeof(std::remove_all_extents_t<T>),
                        StartIndex * sizeof(std::remove_all_extents_t<T>));
  }

  /// Copies data from host to device, where \p Src is a USM pointer and \p Dest
  /// is an opaque image memory handle. An exception is thrown if either \p Src
  /// is nullptr or \p Dest is incomplete. The behavior is undefined if
  /// \p DestImgDesc is inconsistent with the allocated allocated memory
  /// regions.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestImgDesc is the image descriptor.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, ext::oneapi::experimental::image_mem_handle Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from host to device, where \p Src is a USM pointer and \p Dest
  /// is an opaque image memory handle. Allows for a sub-region copy, where
  /// \p SrcOffset , \p DestOffset , and \p CopyExtent are used to determine the
  /// sub-region. Pixel size is determined by \p DestImgDesc . An exception is
  /// thrown if either \p Src is nullptr or \p Dest is incomplete.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcOffset is an offset from the origin where the x, y, and z
  ///                  components are measured in bytes, rows, and slices
  ///                  respectively.
  /// \param SrcExtent is the size of the source memory, measured in
  ///                  pixels. (Pixel size determined by \p DestImgDesc .)
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels. (Pixel size determined by \p DestImgDesc .)
  /// \param DestImgDesc is the destination image descriptor.
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///                   measured in pixels. (Pixel size determined by
  ///                   \p SrcImgDesc .)
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, sycl::range<3> SrcOffset, sycl::range<3> SrcExtent,
      ext::oneapi::experimental::image_mem_handle Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      sycl::range<3> CopyExtent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from host to device, where \p Src is a USM pointer and \p Dest
  /// is an opaque image memory handle. An exception is thrown if either \p Src
  /// is nullptr or \p Dest is incomplete. The behavior is undefined if
  /// \p DestImgDesc is inconsistent with the allocated allocated memory
  /// regions.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestImgDesc is the image descriptor.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, ext::oneapi::experimental::image_mem_handle Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from host to device, where \p Src is a USM pointer and \p Dest
  /// is an opaque image memory handle. Allows for a sub-region copy, where
  /// \p SrcOffset , \p DestOffset , and \p CopyExtent are used to determine the
  /// sub-region. Pixel size is determined by \p DestImgDesc . An exception is
  /// thrown if either \p Src is nullptr or \p Dest is incomplete.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcOffset is an offset from the origin where the x, y, and z
  ///                  components are measured in bytes, rows, and slices
  ///                  respectively.
  /// \param SrcExtent is the size of the source memory, measured in
  ///                  pixels. (Pixel size determined by \p DestImgDesc .)
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels. (Pixel size determined by \p DestImgDesc .)
  /// \param DestImgDesc is the destination image descriptor.
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///                   measured in pixels. (Pixel size determined by
  ///                   \p SrcImgDesc .)
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, sycl::range<3> SrcOffset, sycl::range<3> SrcExtent,
      ext::oneapi::experimental::image_mem_handle Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      sycl::range<3> CopyExtent, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from host to device, where \p Src is a USM pointer and \p Dest
  /// is an opaque image memory handle. An exception is thrown if either \p Src
  /// is nullptr or \p Dest is incomplete. The behavior is undefined if
  /// \p DestImgDesc is inconsistent with the allocated allocated memory
  /// regions.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestImgDesc is the image descriptor.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, ext::oneapi::experimental::image_mem_handle Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from host to device, where \p Src is a USM pointer and \p Dest
  /// is an opaque image memory handle. Allows for a sub-region copy, where
  /// \p SrcOffset , \p DestOffset , and \p CopyExtent are used to determine the
  /// sub-region. Pixel size is determined by \p DestImgDesc . An exception is
  /// thrown if either \p Src is nullptr or \p Dest is incomplete.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcOffset is an offset from the origin where the x, y, and z
  ///                  components are measured in bytes, rows, and slices
  ///                  respectively.
  /// \param SrcExtent is the size of the source memory, measured in
  ///                  pixels. (Pixel size determined by \p DestImgDesc .)
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels. (Pixel size determined by \p DestImgDesc .)
  /// \param DestImgDesc is the destination image descriptor.
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///                   measured in pixels. (Pixel size determined by
  ///                   \p SrcImgDesc .)
  /// \param DepEvents is a vector of events that specifies the kernel
  ///                  dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, sycl::range<3> SrcOffset, sycl::range<3> SrcExtent,
      ext::oneapi::experimental::image_mem_handle Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      sycl::range<3> CopyExtent, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to host, where \p Src is an opaque image memory
  /// handle and \p Dest is a USM pointer. An exception is thrown if either
  /// \p Src is incomplete or \p Dest is nullptr. The behavior is undefined if
  /// \p SrcImgDesc is inconsistent with the allocated memory regions.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src, void *Dest,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to host, where \p Src is an opaque image memory
  /// handle and \p Dest is a USM pointer. Allows for a sub-region copy, where
  /// \p SrcOffset , \p DestOffset , and \p CopyExtent are used to determine the
  /// sub-region. Pixel size is determined by \p SrcImgDesc . An exception is
  /// thrown if either \p Src is nullptr or \p Dest is incomplete.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in pixels.
  ///                  (Pixel size determined by \p SrcImgDesc .)
  /// \param SrcImgDesc is the source image descriptor.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestOffset is an offset from the destination origin where the
  ///                   x, y, and z components are measured in bytes, rows,
  ///                   and slices respectively.
  /// \param DestExtent is the size of the destination memory, measured in
  ///                   pixels. (Pixel size determined by \p SrcImgDesc .)
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///                   measured in pixels. (Pixel size determined by
  ///                   \p SrcImgDesc .)
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc, void *Dest,
      sycl::range<3> DestOffset, sycl::range<3> DestExtent,
      sycl::range<3> CopyExtent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to host, where \p Src is an opaque image memory
  /// handle and \p Dest is a USM pointer. An exception is thrown if either
  /// \p Src is incomplete or \p Dest is nullptr. The behavior is undefined if
  /// \p SrcImgDesc is inconsistent with the allocated memory regions.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src, void *Dest,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to host, where \p Src is an opaque image memory
  /// handle and \p Dest is a USM pointer. Allows for a sub-region copy, where
  /// \p SrcOffset , \p DestOffset , and \p CopyExtent are used to determine the
  /// sub-region. Pixel size is determined by \p SrcImgDesc . An exception is
  /// thrown if either \p Src is nullptr or \p Dest is incomplete.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in pixels.
  ///                  (Pixel size determined by \p SrcImgDesc .)
  /// \param SrcImgDesc is the source image descriptor.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestOffset is an offset from the destination origin where the
  ///                   x, y, and z components are measured in bytes, rows,
  ///                   and slices respectively.
  /// \param DestExtent is the size of the destination memory, measured in
  ///                   pixels. (Pixel size determined by \p SrcImgDesc .)
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///                   measured in pixels. (Pixel size determined by
  ///                   \p SrcImgDesc .)
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc, void *Dest,
      sycl::range<3> DestOffset, sycl::range<3> DestExtent,
      sycl::range<3> CopyExtent, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to host, where \p Src is an opaque image memory
  /// handle and \p Dest is a USM pointer. An exception is thrown if either
  /// \p Src is incomplete or \p Dest is nullptr. The behavior is undefined if
  /// \p SrcImgDesc is inconsistent with the allocated memory regions.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src, void *Dest,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to host, where \p Src is an opaque image memory
  /// handle and \p Dest is a USM pointer. Allows for a sub-region copy, where
  /// \p SrcOffset , \p DestOffset , and \p CopyExtent are used to determine the
  /// sub-region. Pixel size is determined by \p SrcImgDesc . An exception is
  /// thrown if either \p Src is nullptr or \p Dest is incomplete.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in pixels.
  ///                  (Pixel size determined by \p SrcImgDesc .)
  /// \param SrcImgDesc is the source image descriptor.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestOffset is an offset from the destination origin where the
  ///                   x, y, and z components are measured in bytes, rows,
  ///                   and slices respectively.
  /// \param DestExtent is the size of the destination memory, measured in
  ///                   pixels. (Pixel size determined by \p SrcImgDesc .)
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///                   measured in pixels. (Pixel size determined by
  ///                   \p SrcImgDesc .)
  /// \param DepEvents is a vector of events that specifies the kernel
  ///                  dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc, void *Dest,
      sycl::range<3> DestOffset, sycl::range<3> DestExtent,
      sycl::range<3> CopyExtent, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from host to device or device to host, where \p Src and
  /// \p Dest are USM pointers. An exception is thrown if either \p Src is
  /// nullptr or \p Dest is nullptr. The behavior is undefined if
  /// \p DeviceImgDesc is inconsistent with the allocated memory regions or
  /// \p DeviceRowPitch is inconsistent with hardware requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DeviceImgDesc is the device image descriptor.
  /// \param DeviceRowPitch is the pitch of the rows of the memory on the
  ///                       device.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, void *Dest,
      const ext::oneapi::experimental::image_descriptor &DeviceImgDesc,
      size_t DeviceRowPitch,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from host to device or device to host, where \p Src and
  /// \p Dest are USM pointers. Allows for a sub-region copy, where
  /// \p SrcOffset , \p DestOffset , and \p CopyExtent are used to determine the
  /// sub-region. Pixel size is determined by \p DeviceImgDesc . An exception is
  /// thrown if either \p Src is nullptr or \p Dest is nullptr. The behavior is
  /// undefined if \p DeviceRowPitch is inconsistent with hardware requirements
  /// or \p HostExtent is inconsistent with its respective memory region.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcOffset is an destination offset from the origin where the
  ///                  x, y, and z components are measured in bytes, rows,
  ///                  and slices respectively.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestOffset is an destination offset from the origin where the
  ///                   x, y, and z components are measured in bytes, rows,
  ///                   and slices respectively.
  /// \param DeviceImgDesc is the device image descriptor.
  /// \param DeviceRowPitch is the pitch of the rows of the image on the device.
  /// \param HostExtent is the size of the host memory, measured in pixels.
  ///                   (Pixel size determined by \p DeviceImgDesc .)
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///                   measured in pixels. (Pixel size determined by
  ///                   \p DeviceImgDesc .)
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, sycl::range<3> SrcOffset, void *Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DeviceImgDesc,
      size_t DeviceRowPitch, sycl::range<3> HostExtent,
      sycl::range<3> CopyExtent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from host to device or device to host, where \p Src and
  /// \p Dest are USM pointers. An exception is thrown if either \p Src is
  /// nullptr or \p Dest is nullptr. The behavior is undefined if
  /// \p DeviceImgDesc is inconsistent with the allocated memory regions or
  /// \p DeviceRowPitch is inconsistent with hardware requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DeviceImgDesc is the device image descriptor.
  /// \param DeviceRowPitch is the pitch of the rows of the memory on the
  ///                       device.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, void *Dest,
      const ext::oneapi::experimental::image_descriptor &DeviceImgDesc,
      size_t DeviceRowPitch, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from host to device or device to host, where \p Src and
  /// \p Dest are USM pointers. Allows for a sub-region copy, where
  /// \p SrcOffset , \p DestOffset , and \p CopyExtent are used to determine the
  /// sub-region. Pixel size is determined by \p DeviceImgDesc . An exception is
  /// thrown if either \p Src is nullptr or \p Dest is nullptr. The behavior is
  /// undefined if \p DeviceRowPitch is inconsistent with hardware requirements
  /// or \p HostExtent is inconsistent with its respective memory region.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcOffset is an destination offset from the origin where the
  ///                  x, y, and z components are measured in bytes, rows,
  ///                  and slices respectively.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestOffset is an destination offset from the origin where the
  ///                   x, y, and z components are measured in bytes, rows,
  ///                   and slices respectively.
  /// \param DeviceImgDesc is the device image descriptor.
  /// \param DeviceRowPitch is the pitch of the rows of the image on the device.
  /// \param HostExtent is the size of the host memory, measured in pixels.
  ///                   (Pixel size determined by \p DeviceImgDesc .)
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///                   measured in pixels. (Pixel size determined by
  ///                   \p DeviceImgDesc .)
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, sycl::range<3> SrcOffset, void *Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DeviceImgDesc,
      size_t DeviceRowPitch, sycl::range<3> HostExtent,
      sycl::range<3> CopyExtent, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from host to device or device to host, where \p Src and
  /// \p Dest are USM pointers. An exception is thrown if either \p Src is
  /// nullptr or \p Dest is nullptr. The behavior is undefined if
  /// \p DeviceImgDesc is inconsistent with the allocated memory regions or
  /// \p DeviceRowPitch is inconsistent with hardware requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DeviceImgDesc is the device image descriptor.
  /// \param DeviceRowPitch is the pitch of the rows of the memory on the
  ///                       device.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, void *Dest,
      const ext::oneapi::experimental::image_descriptor &DeviceImgDesc,
      size_t DeviceRowPitch, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from host to device or device to host, where \p Src and
  /// \p Dest are USM pointers. Allows for a sub-region copy, where
  /// \p SrcOffset , \p DestOffset , and \p CopyExtent are used to determine the
  /// sub-region. Pixel size is determined by \p DeviceImgDesc . An exception is
  /// thrown if either \p Src is nullptr or \p Dest is nullptr. The behavior is
  /// undefined if \p DeviceRowPitch is inconsistent with hardware requirements
  /// or \p HostExtent is inconsistent with its respective memory region.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcOffset is an destination offset from the origin where the
  ///                  x, y, and z components are measured in bytes, rows,
  ///                  and slices respectively.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestOffset is an destination offset from the origin where the
  ///                   x, y, and z components are measured in bytes, rows,
  ///                   and slices respectively.
  /// \param DeviceImgDesc is the device image descriptor.
  /// \param DeviceRowPitch is the pitch of the rows of the image on the device.
  /// \param HostExtent is the size of the host memory, measured in pixels.
  ///                   (Pixel size determined by \p DeviceImgDesc .)
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///                   measured in pixels. (Pixel size determined by
  ///                   \p DeviceImgDesc .)
  /// \param DepEvents is a vector of events that specifies the kernel
  ///                  dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, sycl::range<3> SrcOffset, void *Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DeviceImgDesc,
      size_t DeviceRowPitch, sycl::range<3> HostExtent,
      sycl::range<3> CopyExtent, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device, where \p Src and \p Dest are opaque
  /// image memory handles. An exception is thrown if either \p Src or \p Dest
  /// are incomplete. The behavior is undefined if \p SrcImgDesc or
  /// \p DestImgDesc are inconsistent with their respective allocated memory
  /// regions.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestImgDesc is the destination image descriptor.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      ext::oneapi::experimental::image_mem_handle Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device, where \p Src and \p Dest are opaque
  /// image memory handles. Allows for a sub-region copy, where \p SrcOffset ,
  /// \p DestOffset and \p CopyExtent are used to determine the sub-region.
  /// Pixel size is determined by \p SrcImgDesc . An exception is thrown if
  /// either \p Src or \p Dest is incomplete.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in pixels.
  ///                  (Pixel size determined by \p SrcImgDesc .)
  /// \param SrcImgDesc is the source image descriptor.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels. (Pixel size determined by \p SrcImgDesc .)
  /// \param DestImgDesc is the destination image descriptor.
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///                   measured in pixels. (Pixel size determined by
  ///                   \p SrcImgDesc .)
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      ext::oneapi::experimental::image_mem_handle Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      sycl::range<3> CopyExtent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device, where \p Src and \p Dest are opaque
  /// image memory handles. An exception is thrown if either \p Src or \p Dest
  /// are incomplete. The behavior is undefined if \p SrcImgDesc or
  /// \p DestImgDesc are inconsistent with their respective allocated memory
  /// regions.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestImgDesc is the destination image descriptor.
  /// \param DepEvent is an events that specifies the kernel dependency.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      ext::oneapi::experimental::image_mem_handle Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device, where \p Src and \p Dest are opaque
  /// image memory handles. Allows for a sub-region copy, where \p SrcOffset ,
  /// \p DestOffset and \p CopyExtent are used to determine the sub-region.
  /// Pixel size is determined by \p SrcImgDesc . An exception is thrown if
  /// either \p Src or \p Dest is incomplete.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in pixels.
  ///                  (Pixel size determined by \p SrcImgDesc .)
  /// \param SrcImgDesc is the source image descriptor.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels. (Pixel size determined by \p SrcImgDesc .)
  /// \param DestImgDesc is the destination image descriptor.
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///                   measured in pixels. (Pixel size determined by
  ///                   \p SrcImgDesc .)
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      ext::oneapi::experimental::image_mem_handle Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      sycl::range<3> CopyExtent, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device, where \p Src and \p Dest are opaque
  /// image memory handles. An exception is thrown if either \p Src or \p Dest
  /// are incomplete. The behavior is undefined if \p SrcImgDesc or
  /// \p DestImgDesc are inconsistent with their respective allocated memory
  /// regions.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestImgDesc is the destination image descriptor.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      ext::oneapi::experimental::image_mem_handle Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device, where \p Src and \p Dest are opaque
  /// image memory handles. Allows for a sub-region copy, where \p SrcOffset ,
  /// \p DestOffset and \p CopyExtent are used to determine the sub-region.
  /// Pixel size is determined by \p SrcImgDesc . An exception is thrown if
  /// either \p Src or \p Dest is incomplete.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in pixels.
  ///                  (Pixel size determined by \p SrcImgDesc .)
  /// \param SrcImgDesc is the source image descriptor.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels. (Pixel size determined by \p SrcImgDesc .)
  /// \param DestImgDesc is the destination image descriptor.
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///                   measured in pixels. (Pixel size determined by
  ///                   \p SrcImgDesc .)
  /// \param DepEvents is a vector of events that specifies the kernel
  ///                  dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      ext::oneapi::experimental::image_mem_handle Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      sycl::range<3> CopyExtent, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device, where \p Src is an opaque image memory
  /// handle and \p Dest is a USM pointer. An exception is thrown if either
  /// \p Src is incomplete or \p Dest is nullptr. The behavior is undefined if
  /// \p SrcImgDesc or \p DestImgDesc are inconsistent with their respective
  /// allocated memory regions or \p DestRowPitch is inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestImgDesc is the destination image descriptor.
  /// \param DestRowPitch is the pitch of the rows of the destination memory.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc, void *Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      size_t DestRowPitch,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device, where \p Src is an opaque image memory
  /// handle and \p Dest is a USM pointer. Allows for a sub-region copy, where
  /// \p SrcOffset, \p DestOffset and \p CopyExtent are used to determine the
  /// sub-region. Pixel size is determined by \p SrcImgDesc . An exception is
  /// thrown if either \p Src is incomplete or \p Dest is nullptr. The behavior
  /// is undefined if \p DestRowPitch is inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in Pixels
  ///                   (Pixel size determined by \p SrcImgDesc .)
  /// \param SrcImgDesc is the source image descriptor
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels. (Pixel size determined by \p SrcImgDesc .)
  /// \param DestImgDesc is the destination image descriptor.
  /// \param DestRowPitch is the pitch of the rows of the destination memory.
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///               measured in pixels. (Pixel size determined by
  ///               \p SrcImgDesc .)
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc, void *Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      size_t DestRowPitch, sycl::range<3> CopyExtent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device, where \p Src is an opaque image memory
  /// handle and \p Dest is a USM pointer. An exception is thrown if either
  /// \p Src is incomplete or \p Dest is nullptr. The behavior is undefined if
  /// \p SrcImgDesc or \p DestImgDesc are inconsistent with their respective
  /// allocated memory regions or \p DestRowPitch is inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestImgDesc is the destination image descriptor.
  /// \param DestRowPitch is the pitch of the rows of the destination memory.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc, void *Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      size_t DestRowPitch, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device, where \p Src is an opaque image memory
  /// handle and \p Dest is a USM pointer. Allows for a sub-region copy, where
  /// \p SrcOffset, \p DestOffset and \p CopyExtent are used to determine the
  /// sub-region. Pixel size is determined by \p SrcImgDesc . An exception is
  /// thrown if either \p Src is incomplete or \p Dest is nullptr. The behavior
  /// is undefined if \p DestRowPitch is inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in Pixels
  ///                   (Pixel size determined by \p SrcImgDesc .)
  /// \param SrcImgDesc is the source image descriptor
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels. (Pixel size determined by \p SrcImgDesc .)
  /// \param DestImgDesc is the destination image descriptor.
  /// \param DestRowPitch is the pitch of the rows of the destination memory.
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///               measured in pixels. (Pixel size determined by
  ///               \p SrcImgDesc .)
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc, void *Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      size_t DestRowPitch, sycl::range<3> CopyExtent, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device, where \p Src is an opaque image memory
  /// handle and \p Dest is a USM pointer. An exception is thrown if either
  /// \p Src is incomplete or \p Dest is nullptr. The behavior is undefined if
  /// \p SrcImgDesc or \p DestImgDesc are inconsistent with their respective
  /// allocated memory regions or \p DestRowPitch is inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestImgDesc is the destination image descriptor.
  /// \param DestRowPitch is the pitch of the rows of the destination memory.
  /// \param DepEvents is a vector of events that specifies the kernel
  ///                  dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc, void *Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      size_t DestRowPitch, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device, where \p Src is an opaque image memory
  /// handle and \p Dest is a USM pointer. Allows for a sub-region copy, where
  /// \p SrcOffset, \p DestOffset and \p CopyExtent are used to determine the
  /// sub-region. Pixel size is determined by \p SrcImgDesc . An exception is
  /// thrown if either \p Src is incomplete or \p Dest is nullptr. The behavior
  /// is undefined if \p DestRowPitch is inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is an opaque image memory handle to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in Pixels
  ///                   (Pixel size determined by \p SrcImgDesc .)
  /// \param SrcImgDesc is the source image descriptor
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels. (Pixel size determined by \p SrcImgDesc .)
  /// \param DestImgDesc is the destination image descriptor.
  /// \param DestRowPitch is the pitch of the rows of the destination memory.
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///               measured in pixels. (Pixel size determined by
  ///               \p SrcImgDesc .)
  /// \param DepEvents is a vector of events that specifies the kernel
  ///                  dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const ext::oneapi::experimental::image_mem_handle Src,
      sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc, void *Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      size_t DestRowPitch, sycl::range<3> CopyExtent,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device memory, where \p Src is USM pointer and
  /// \p Dest is an opaque image memory handle. An exception is thrown if either
  /// \p Src is nullptr or \p Dest is incomplete. The behavior is undefined if
  /// \p SrcImgDesc or \p DestImgDesc are inconsistent with their respective
  /// allocated memory regions or \p SrcRowPitch is inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param SrcRowPitch is the pitch of the rows of the source memory.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestImgDesc is the destination image descriptor.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      size_t SrcRowPitch, ext::oneapi::experimental::image_mem_handle Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device memory, where \p Src is USM pointer and
  /// \p Dest is an opaque image memory handle. Allows for a sub-region
  /// copy, where \p SrcOffset, \p DestOffset and \p CopyExtent are used to
  /// determine the sub-region. Pixel size is determined by \p SrcImgDesc . An
  /// exception is thrown if either \p Src is nullptr or \p Dest is incomplete.
  /// The behavior is undefined if \p SrcRowPitch is inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in pixels
  ///                   (pixel size determined by \p SrcImgDesc )
  /// \param SrcImgDesc is the source image descriptor
  /// \param SrcRowPitch is the pitch of the rows of the destination memory.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels (pixel size determined by \p SrcImgDesc )
  /// \param DestImgDesc is the destination image descriptor
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///               measured in pixels (pixel size determined by
  ///               \p SrcImgDesc )
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      size_t SrcRowPitch, ext::oneapi::experimental::image_mem_handle Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      sycl::range<3> CopyExtent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device memory, where \p Src is USM pointer and
  /// \p Dest is an opaque image memory handle. An exception is thrown if either
  /// \p Src is nullptr or \p Dest is incomplete. The behavior is undefined if
  /// \p SrcImgDesc or \p DestImgDesc are inconsistent with their respective
  /// allocated memory regions or \p SrcRowPitch is inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param SrcRowPitch is the pitch of the rows of the source memory.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestImgDesc is the destination image descriptor.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      size_t SrcRowPitch, ext::oneapi::experimental::image_mem_handle Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device memory, where \p Src is USM pointer and
  /// \p Dest is an opaque image memory handle. Allows for a sub-region
  /// copy, where \p SrcOffset, \p DestOffset and \p CopyExtent are used to
  /// determine the sub-region. Pixel size is determined by \p SrcImgDesc . An
  /// exception is thrown if either \p Src is nullptr or \p Dest is incomplete.
  /// The behavior is undefined if \p SrcRowPitch is inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in pixels
  ///                   (pixel size determined by \p SrcImgDesc )
  /// \param SrcImgDesc is the source image descriptor
  /// \param SrcRowPitch is the pitch of the rows of the destination memory.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels (pixel size determined by \p SrcImgDesc )
  /// \param DestImgDesc is the destination image descriptor
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///               measured in pixels (pixel size determined by
  ///               \p SrcImgDesc )
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      size_t SrcRowPitch, ext::oneapi::experimental::image_mem_handle Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      sycl::range<3> CopyExtent, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device memory, where \p Src is USM pointer and
  /// \p Dest is an opaque image memory handle. An exception is thrown if either
  /// \p Src is nullptr or \p Dest is incomplete. The behavior is undefined if
  /// \p SrcImgDesc or \p DestImgDesc are inconsistent with their respective
  /// allocated memory regions or \p SrcRowPitch is inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param SrcRowPitch is the pitch of the rows of the source memory.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestImgDesc is the destination image descriptor.
  /// \param DepEvents is a vector of events that specifies the kernel
  ///                  dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      size_t SrcRowPitch, ext::oneapi::experimental::image_mem_handle Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from device to device memory, where \p Src is USM pointer and
  /// \p Dest is an opaque image memory handle. Allows for a sub-region
  /// copy, where \p SrcOffset, \p DestOffset and \p CopyExtent are used to
  /// determine the sub-region. Pixel size is determined by \p SrcImgDesc . An
  /// exception is thrown if either \p Src is nullptr or \p Dest is incomplete.
  /// The behavior is undefined if \p SrcRowPitch is inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in pixels
  ///                   (pixel size determined by \p SrcImgDesc )
  /// \param SrcImgDesc is the source image descriptor
  /// \param SrcRowPitch is the pitch of the rows of the destination memory.
  /// \param Dest is an opaque image memory handle to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels (pixel size determined by \p SrcImgDesc )
  /// \param DestImgDesc is the destination image descriptor
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///               measured in pixels (pixel size determined by
  ///               \p SrcImgDesc )
  /// \param DepEvents is a vector of events that specifies the kernel
  ///                  dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      size_t SrcRowPitch, ext::oneapi::experimental::image_mem_handle Dest,
      sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      sycl::range<3> CopyExtent, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from DtoD or HtoH memory, where \p Src and \p Dest are USM
  /// pointers. An exception is thrown if either \p Src or \p Dest are nullptr.
  /// The behavior is undefined if \p SrcImgDesc or \p DestImgDesc are
  /// inconsistent with their respective allocated memory regions or
  /// \p SrcRowPitch or \p DestRowPitch are inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param SrcRowPitch is the pitch of the rows of the source memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestImgDesc is the destination image descriptor.
  /// \param SrcRowPitch is the pitch of the rows of the destination memory.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      size_t SrcRowPitch, void *Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      size_t DestRowPitch,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from DtoD or HtoH memory, where \p Src and \p Dest are USM
  /// pointers. Allows for a sub-region copy, where \p SrcOffset, \p DestOffset
  /// and \p CopyExtent are used to determine the sub-region. Pixel size is
  /// determined by \p SrcImgDesc . An exception is thrown if either \p Src or
  /// \p Dest are nullptr. The behavior is undefined if \p SrcRowPitch or
  /// \p DestRowPitch are inconsistent with hardware requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in pixels
  ///                   (pixel size determined by \p SrcImgDesc )
  /// \param SrcImgDesc is the source image descriptor
  /// \param SrcRowPitch is the pitch of the rows of the destination memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels (pixel size determined by \p SrcImgDesc )
  /// \param DestImgDesc is the destination image descriptor
  /// \param DestRowPitch is the pitch of the rows of the destination memory.
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///               measured in pixels (pixel size determined by
  ///               \p SrcImgDesc )
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      size_t SrcRowPitch, void *Dest, sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      size_t DestRowPitch, sycl::range<3> CopyExtent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from DtoD or HtoH memory, where \p Src and \p Dest are USM
  /// pointers. An exception is thrown if either \p Src or \p Dest are nullptr.
  /// The behavior is undefined if \p SrcImgDesc or \p DestImgDesc are
  /// inconsistent with their respective allocated memory regions or
  /// \p SrcRowPitch or \p DestRowPitch are inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param SrcRowPitch is the pitch of the rows of the source memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestImgDesc is the destination image descriptor.
  /// \param SrcRowPitch is the pitch of the rows of the destination memory.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      size_t SrcRowPitch, void *Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      size_t DestRowPitch, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from DtoD or HtoH memory, where \p Src and \p Dest are USM
  /// pointers. Allows for a sub-region copy, where \p SrcOffset, \p DestOffset
  /// and \p CopyExtent are used to determine the sub-region. Pixel size is
  /// determined by \p SrcImgDesc . An exception is thrown if either \p Src or
  /// \p Dest are nullptr. The behavior is undefined if \p SrcRowPitch or
  /// \p DestRowPitch are inconsistent with hardware requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in pixels
  ///                   (pixel size determined by \p SrcImgDesc )
  /// \param SrcImgDesc is the source image descriptor
  /// \param SrcRowPitch is the pitch of the rows of the destination memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels (pixel size determined by \p SrcImgDesc )
  /// \param DestImgDesc is the destination image descriptor
  /// \param DestRowPitch is the pitch of the rows of the destination memory.
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///               measured in pixels (pixel size determined by
  ///               \p SrcImgDesc )
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      size_t SrcRowPitch, void *Dest, sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      size_t DestRowPitch, sycl::range<3> CopyExtent, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from DtoD or HtoH memory, where \p Src and \p Dest are USM
  /// pointers. An exception is thrown if either \p Src or \p Dest are nullptr.
  /// The behavior is undefined if \p SrcImgDesc or \p DestImgDesc are
  /// inconsistent with their respective allocated memory regions or
  /// \p SrcRowPitch or \p DestRowPitch are inconsistent with hardware
  /// requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcImgDesc is the source image descriptor.
  /// \param SrcRowPitch is the pitch of the rows of the source memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestImgDesc is the destination image descriptor.
  /// \param SrcRowPitch is the pitch of the rows of the destination memory.
  /// \param DepEvents is a vector of events that specifies the kernel
  ///                  dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      size_t SrcRowPitch, void *Dest,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      size_t DestRowPitch, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Copies data from DtoD or HtoH memory, where \p Src and \p Dest are USM
  /// pointers. Allows for a sub-region copy, where \p SrcOffset, \p DestOffset
  /// and \p CopyExtent are used to determine the sub-region. Pixel size is
  /// determined by \p SrcImgDesc . An exception is thrown if either \p Src or
  /// \p Dest are nullptr. The behavior is undefined if \p SrcRowPitch or
  /// \p DestRowPitch are inconsistent with hardware requirements.
  ///
  /// \param Src is a USM pointer to the source memory.
  /// \param SrcOffset is an offset from the source origin measured in pixels
  ///                   (pixel size determined by \p SrcImgDesc )
  /// \param SrcImgDesc is the source image descriptor
  /// \param SrcRowPitch is the pitch of the rows of the destination memory.
  /// \param Dest is a USM pointer to the destination memory.
  /// \param DestOffset is an offset from the destination origin measured in
  ///                   pixels (pixel size determined by \p SrcImgDesc )
  /// \param DestImgDesc is the destination image descriptor
  /// \param DestRowPitch is the pitch of the rows of the destination memory.
  /// \param CopyExtent is the width, height, and depth of the region to copy
  ///               measured in pixels (pixel size determined by
  ///               \p SrcImgDesc )
  /// \param DepEvents is a vector of events that specifies the kernel
  ///                  dependencies.
  /// \return an event representing the copy operation.
  event ext_oneapi_copy(
      const void *Src, sycl::range<3> SrcOffset,
      const ext::oneapi::experimental::image_descriptor &SrcImgDesc,
      size_t SrcRowPitch, void *Dest, sycl::range<3> DestOffset,
      const ext::oneapi::experimental::image_descriptor &DestImgDesc,
      size_t DestRowPitch, sycl::range<3> CopyExtent,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Instruct the queue with a non-blocking wait on an external semaphore.
  /// An exception is thrown if \p extSemaphore is incomplete, or if the
  /// type of semaphore requires an explicit value to wait upon.
  ///
  /// \param extSemaphore is an opaque external semaphore object.
  /// \return an event representing the wait operation.
  event ext_oneapi_wait_external_semaphore(
      sycl::ext::oneapi::experimental::external_semaphore extSemaphore,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return submit(
        [&](handler &CGH) {
          CGH.ext_oneapi_wait_external_semaphore(extSemaphore);
        },
        TlsCodeLocCapture.query());
  }

  /// Instruct the queue with a non-blocking wait on an external semaphore.
  /// An exception is thrown if \p extSemaphore is incomplete, or if the
  /// type of semaphore requires an explicit value to wait upon.
  ///
  /// \param extSemaphore is an opaque external semaphore object
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the wait operation.
  event ext_oneapi_wait_external_semaphore(
      sycl::ext::oneapi::experimental::external_semaphore extSemaphore,
      event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Instruct the queue with a non-blocking wait on an external semaphore.
  /// An exception is thrown if \p extSemaphore is incomplete, or if the
  /// type of semaphore requires an explicit value to wait upon.
  ///
  /// \param extSemaphore is an opaque external semaphore object.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing the wait operation.
  event ext_oneapi_wait_external_semaphore(
      sycl::ext::oneapi::experimental::external_semaphore extSemaphore,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Instruct the queue with a non-blocking wait on an external semaphore.
  /// An exception is thrown if \p extSemaphore is incomplete, or if the
  /// type of semaphore does not support waiting on an explicitly passed value.
  ///
  /// \param extSemaphore is an opaque external semaphore object
  /// \param WaitValue is the value that this semaphore will wait upon, until it
  ///                  allows any further commands to execute on the queue.
  /// \return an event representing the wait operation.
  event ext_oneapi_wait_external_semaphore(
      sycl::ext::oneapi::experimental::external_semaphore extSemaphore,
      uint64_t WaitValue,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Instruct the queue with a non-blocking wait on an external semaphore.
  /// An exception is thrown if \p extSemaphore is incomplete, or if the
  /// type of semaphore does not support waiting on an explicitly passed value.
  ///
  /// \param extSemaphore is an opaque external semaphore object
  /// \param WaitValue is the value that this semaphore will wait upon, until it
  ///                  allows any further commands to execute on the queue.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the wait operation.
  event ext_oneapi_wait_external_semaphore(
      sycl::ext::oneapi::experimental::external_semaphore extSemaphore,
      uint64_t WaitValue, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Instruct the queue with a non-blocking wait on an external semaphore.
  /// An exception is thrown if \p extSemaphore is incomplete, or if the
  /// type of semaphore does not support waiting on an explicitly passed value.
  ///
  /// \param extSemaphore is an opaque external semaphore object
  /// \param WaitValue is the value that this semaphore will wait upon, until it
  ///                  allows any further commands to execute on the queue.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing the wait operation.
  event ext_oneapi_wait_external_semaphore(
      sycl::ext::oneapi::experimental::external_semaphore extSemaphore,
      uint64_t WaitValue, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Instruct the queue to signal the external semaphore once all previous
  /// commands have completed execution.
  /// An exception is thrown if \p extSemaphore is incomplete, or if the
  /// type of semaphore requires an explicit value to signal.
  ///
  /// \param extSemaphore is an opaque external semaphore object
  /// \return an event representing the signal operation.
  event ext_oneapi_signal_external_semaphore(
      sycl::ext::oneapi::experimental::external_semaphore extSemaphore,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Instruct the queue to signal the external semaphore once all previous
  /// commands have completed execution.
  /// An exception is thrown if \p extSemaphore is incomplete, or if the
  /// type of semaphore requires an explicit value to signal.
  ///
  /// \param extSemaphore is an opaque external semaphore object
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the signal operation.
  event ext_oneapi_signal_external_semaphore(
      sycl::ext::oneapi::experimental::external_semaphore extSemaphore,
      event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Instruct the queue to signal the external semaphore once all previous
  /// commands have completed execution.
  /// An exception is thrown if \p extSemaphore is incomplete, or if the
  /// type of semaphore requires an explicit value to signal.
  ///
  /// \param extSemaphore is an opaque external semaphore object
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing the signal operation.
  event ext_oneapi_signal_external_semaphore(
      sycl::ext::oneapi::experimental::external_semaphore extSemaphore,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Instruct the queue to signal the external semaphore once all previous
  /// commands have completed execution.
  /// An exception is thrown if \p extSemaphore is incomplete, or if the
  /// type of semaphore does not support signalling an explicitly passed value.
  ///
  /// \param extSemaphore is an opaque external semaphore object
  /// \param SignalValue is the value that this semaphore signal, once all
  ///                    prior opeartions on the queue complete.
  /// \return an event representing the signal operation.
  event ext_oneapi_signal_external_semaphore(
      sycl::ext::oneapi::experimental::external_semaphore extSemaphore,
      uint64_t SignalValue,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Instruct the queue to signal the external semaphore once all previous
  /// commands have completed execution.
  /// An exception is thrown if \p extSemaphore is incomplete, or if the
  /// type of semaphore does not support signalling an explicitly passed value.
  ///
  /// \param extSemaphore is an opaque external semaphore object
  /// \param SignalValue is the value that this semaphore signal, once all
  ///                    prior opeartions on the queue complete.
  /// \param DepEvent is an event that specifies the kernel dependencies.
  /// \return an event representing the signal operation.
  event ext_oneapi_signal_external_semaphore(
      sycl::ext::oneapi::experimental::external_semaphore extSemaphore,
      uint64_t SignalValue, event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// Instruct the queue to signal the external semaphore once all previous
  /// commands have completed execution.
  /// An exception is thrown if \p extSemaphore is incomplete, or if the
  /// type of semaphore does not support signalling an explicitly passed value.
  ///
  /// \param extSemaphore is an opaque external semaphore object
  /// \param SignalValue is the value that this semaphore signal, once all
  ///                    prior opeartions on the queue complete.
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies.
  /// \return an event representing the signal operation.
  event ext_oneapi_signal_external_semaphore(
      sycl::ext::oneapi::experimental::external_semaphore SemaphoreHandle,
      uint64_t SignalValue, const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current());

  /// single_task version with a kernel represented as a lambda.
  ///
  /// \param Properties is the kernel properties.
  /// \param KernelFunc is the Kernel functor or lambda
  /// \param CodeLoc contains the code location of user code
  template <typename KernelName = detail::auto_name, typename KernelType,
            typename PropertiesT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> single_task(PropertiesT Properties, const KernelType &KernelFunc,
                         const detail::code_location &CodeLoc =
                             detail::code_location::current()) {
    static_assert(
        (detail::check_fn_signature<std::remove_reference_t<KernelType>,
                                    void()>::value ||
         detail::check_fn_signature<std::remove_reference_t<KernelType>,
                                    void(kernel_handler)>::value),
        "sycl::queue.single_task() requires a kernel instead of command group. "
        "Use queue.submit() instead");

    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);

    // TODO The handler-less path does not support kernel functions
    // with the kernel_handler type argument yet.
    if constexpr (!(detail::KernelLambdaHasKernelHandlerArgT<KernelType,
                                                             void>::value)) {
      return detail::submit_kernel_direct_single_task<KernelName, true>(
          *this, KernelFunc, {}, Properties, TlsCodeLocCapture.query());
    } else {
      return submit(
          [&](handler &CGH) {
            CGH.template single_task<KernelName, KernelType, PropertiesT>(
                Properties, KernelFunc);
          },
          TlsCodeLocCapture.query());
    }
  }

  /// single_task version with a kernel represented as a lambda.
  ///
  /// \param KernelFunc is the Kernel functor or lambda
  /// \param CodeLoc contains the code location of user code
  template <typename KernelName = detail::auto_name, typename KernelType>
  event single_task(
      const KernelType &KernelFunc,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return single_task<KernelName, KernelType>(
        ext::oneapi::experimental::empty_properties_t{}, KernelFunc, CodeLoc);
  }

  /// single_task version with a kernel represented as a lambda.
  ///
  /// \param DepEvent is an event that specifies the kernel dependencies
  /// \param Properties is the kernel properties.
  /// \param KernelFunc is the Kernel functor or lambda
  /// \param CodeLoc contains the code location of user code
  template <typename KernelName = detail::auto_name, typename KernelType,
            typename PropertiesT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> single_task(event DepEvent, PropertiesT Properties,
                         const KernelType &KernelFunc,
                         const detail::code_location &CodeLoc =
                             detail::code_location::current()) {
    static_assert(
        (detail::check_fn_signature<std::remove_reference_t<KernelType>,
                                    void()>::value ||
         detail::check_fn_signature<std::remove_reference_t<KernelType>,
                                    void(kernel_handler)>::value),
        "sycl::queue.single_task() requires a kernel instead of command group. "
        "Use queue.submit() instead");

    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);

    // TODO The handler-less path does not support kernel functions
    // with the kernel_handler type argument yet.
    if constexpr (!(detail::KernelLambdaHasKernelHandlerArgT<KernelType,
                                                             void>::value)) {
      return detail::submit_kernel_direct_single_task<KernelName, true>(
          *this, KernelFunc, sycl::span<const event>(&DepEvent, 1), Properties,
          TlsCodeLocCapture.query());
    } else {
      return submit(
          [&](handler &CGH) {
            CGH.depends_on(DepEvent);
            CGH.template single_task<KernelName, KernelType, PropertiesT>(
                Properties, KernelFunc);
          },
          TlsCodeLocCapture.query());
    }
  }

  /// single_task version with a kernel represented as a lambda.
  ///
  /// \param DepEvent is an event that specifies the kernel dependencies
  /// \param KernelFunc is the Kernel functor or lambda
  /// \param CodeLoc contains the code location of user code
  template <typename KernelName = detail::auto_name, typename KernelType>
  event single_task(
      event DepEvent, const KernelType &KernelFunc,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return single_task<KernelName, KernelType>(
        DepEvent, ext::oneapi::experimental::empty_properties_t{}, KernelFunc,
        CodeLoc);
  }

  /// single_task version with a kernel represented as a lambda.
  ///
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies
  /// \param Properties is the kernel properties.
  /// \param KernelFunc is the Kernel functor or lambda
  /// \param CodeLoc contains the code location of user code
  template <typename KernelName = detail::auto_name, typename KernelType,
            typename PropertiesT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> single_task(const std::vector<event> &DepEvents,
                         PropertiesT Properties, const KernelType &KernelFunc,
                         const detail::code_location &CodeLoc =
                             detail::code_location::current()) {
    static_assert(
        (detail::check_fn_signature<std::remove_reference_t<KernelType>,
                                    void()>::value ||
         detail::check_fn_signature<std::remove_reference_t<KernelType>,
                                    void(kernel_handler)>::value),
        "sycl::queue.single_task() requires a kernel instead of command group. "
        "Use queue.submit() instead");

    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);

    // TODO The handler-less path does not support kernel functions
    // with the kernel_handler type argument yet.
    if constexpr (!(detail::KernelLambdaHasKernelHandlerArgT<KernelType,
                                                             void>::value)) {
      return detail::submit_kernel_direct_single_task<KernelName, true>(
          *this, KernelFunc, DepEvents, Properties, TlsCodeLocCapture.query());
    } else {
      return submit(
          [&](handler &CGH) {
            CGH.depends_on(DepEvents);
            CGH.template single_task<KernelName, KernelType, PropertiesT>(
                Properties, KernelFunc);
          },
          TlsCodeLocCapture.query());
    }
  }

  /// single_task version with a kernel represented as a lambda.
  ///
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies
  /// \param KernelFunc is the Kernel functor or lambda
  /// \param CodeLoc contains the code location of user code
  template <typename KernelName = detail::auto_name, typename KernelType>
  event single_task(
      const std::vector<event> &DepEvents, const KernelType &KernelFunc,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return single_task<KernelName, KernelType>(
        DepEvents, ext::oneapi::experimental::empty_properties_t{}, KernelFunc,
        CodeLoc);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param Properties is the kernel properties.
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename PropertiesT,
            typename... RestT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> parallel_for(range<1> Range, PropertiesT Properties,
                          RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, Properties, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename... RestT>
  event parallel_for(range<1> Range, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param Properties is the kernel properties.
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename PropertiesT,
            typename... RestT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> parallel_for(range<2> Range, PropertiesT Properties,
                          RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, Properties, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename... RestT>
  event parallel_for(range<2> Range, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param Properties is the kernel properties.
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename PropertiesT,
            typename... RestT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> parallel_for(range<3> Range, PropertiesT Properties,
                          RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, Properties, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename... RestT>
  event parallel_for(range<3> Range, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvent is an event that specifies the kernel dependencies
  /// \param Properties is the kernel properties.
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename PropertiesT,
            typename... RestT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> parallel_for(range<1> Range, event DepEvent,
                          PropertiesT Properties, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, DepEvent, Properties, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvent is an event that specifies the kernel dependencies
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename... RestT>
  event parallel_for(range<1> Range, event DepEvent, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, DepEvent, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvent is an event that specifies the kernel dependencies
  /// \param Properties is the kernel properties.
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename PropertiesT,
            typename... RestT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> parallel_for(range<2> Range, event DepEvent,
                          PropertiesT Properties, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, DepEvent, Properties, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvent is an event that specifies the kernel dependencies
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename... RestT>
  event parallel_for(range<2> Range, event DepEvent, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, DepEvent, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvent is an event that specifies the kernel dependencies
  /// \param Properties is the kernel properties.
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename PropertiesT,
            typename... RestT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> parallel_for(range<3> Range, event DepEvent,
                          PropertiesT Properties, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, DepEvent, Properties, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvent is an event that specifies the kernel dependencies
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename... RestT>
  event parallel_for(range<3> Range, event DepEvent, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, DepEvent, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies
  /// \param Properties is the kernel properties.
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename PropertiesT,
            typename... RestT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> parallel_for(range<1> Range, const std::vector<event> &DepEvents,
                          PropertiesT Properties, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, DepEvents, Properties, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename... RestT>
  event parallel_for(range<1> Range, const std::vector<event> &DepEvents,
                     RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, DepEvents, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies
  /// \param Properties is the kernel properties.
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename PropertiesT,
            typename... RestT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> parallel_for(range<2> Range, const std::vector<event> &DepEvents,
                          PropertiesT Properties, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, DepEvents, Properties, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename... RestT>
  event parallel_for(range<2> Range, const std::vector<event> &DepEvents,
                     RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, DepEvents, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies
  /// \param Properties is the kernel properties.
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename PropertiesT,
            typename... RestT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> parallel_for(range<3> Range, const std::vector<event> &DepEvents,
                          PropertiesT Properties, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, DepEvents, Properties, Rest...);
  }

  /// parallel_for version with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, typename... RestT>
  event parallel_for(range<3> Range, const std::vector<event> &DepEvents,
                     RestT &&...Rest) {
    return parallel_for_impl<KernelName>(Range, DepEvents, Rest...);
  }

  // While other shortcuts with offsets are able to go through parallel_for(...,
  // RestT &&...Rest), those that accept dependency events vector have to be
  // overloaded to allow implicit construction from an init-list.
  /// parallel_for version with a kernel represented as a lambda + range and
  /// offset that specify global size and global offset correspondingly.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param WorkItemOffset specifies the offset for each work item id
  /// \param KernelFunc is the Kernel functor or lambda
  /// \param CodeLoc contains the code location of user code
  template <typename KernelName = detail::auto_name, typename KernelType,
            int Dim>
  event parallel_for(range<Dim> Range, id<Dim> WorkItemOffset,
                     const std::vector<event> &DepEvents,
                     const KernelType &KernelFunc) {
    static_assert(1 <= Dim && Dim <= 3, "Invalid number of dimensions");
    return parallel_for_impl<KernelName>(Range, WorkItemOffset, DepEvents,
                                         KernelFunc);
  }

  /// parallel_for version with a kernel represented as a lambda + range and
  /// offset that specify global size and global offset correspondingly.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param WorkItemOffset specifies the offset for each work item id
  /// \param KernelFunc is the Kernel functor or lambda
  /// \param CodeLoc contains the code location of user code
  template <typename KernelName = detail::auto_name, typename KernelType,
            int Dims>
  __SYCL2020_DEPRECATED("offsets are deprecated in SYCL 2020")
  event parallel_for_impl(range<Dims> Range, id<Dims> WorkItemOffset,
                          const KernelType &KernelFunc) {
    // Actual code location needs to be captured from KernelInfo object.
    const detail::code_location CodeLoc = {};
    return submit(
        [&](handler &CGH) {
          CGH.template parallel_for<KernelName>(Range, WorkItemOffset,
                                                KernelFunc);
        },
        CodeLoc);
  }

  /// parallel_for version with a kernel represented as a lambda + range and
  /// offset that specify global size and global offset correspondingly.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param WorkItemOffset specifies the offset for each work item id
  /// \param DepEvent is an event that specifies the kernel dependencies
  /// \param KernelFunc is the Kernel functor or lambda
  /// \param CodeLoc contains the code location of user code
  template <typename KernelName = detail::auto_name, typename KernelType,
            int Dims>
  __SYCL2020_DEPRECATED("offsets are deprecated in SYCL 2020")
  event parallel_for_impl(range<Dims> Range, id<Dims> WorkItemOffset,
                          event DepEvent, const KernelType &KernelFunc) {
    // Actual code location needs to be captured from KernelInfo object.
    const detail::code_location CodeLoc = {};
    return submit(
        [&](handler &CGH) {
          CGH.depends_on(DepEvent);
          CGH.template parallel_for<KernelName>(Range, WorkItemOffset,
                                                KernelFunc);
        },
        CodeLoc);
  }

  /// parallel_for version with a kernel represented as a lambda + range and
  /// offset that specify global size and global offset correspondingly.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param WorkItemOffset specifies the offset for each work item id
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies
  /// \param KernelFunc is the Kernel functor or lambda
  /// \param CodeLoc contains the code location of user code
  template <typename KernelName = detail::auto_name, typename KernelType,
            int Dims>
  __SYCL2020_DEPRECATED("offsets are deprecated in SYCL 2020")
  event parallel_for_impl(range<Dims> Range, id<Dims> WorkItemOffset,
                          const std::vector<event> &DepEvents,
                          const KernelType &KernelFunc) {
    // Actual code location needs to be captured from KernelInfo object.
    const detail::code_location CodeLoc = {};
    return submit(
        [&](handler &CGH) {
          CGH.depends_on(DepEvents);
          CGH.template parallel_for<KernelName>(Range, WorkItemOffset,
                                                KernelFunc);
        },
        CodeLoc);
  }

  /// parallel_for version with a kernel represented as a lambda + nd_range that
  /// specifies global, local sizes and offset.
  ///
  /// \param Range specifies the global and local work spaces of the kernel
  /// \param Properties is the kernel properties.
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, int Dims,
            typename PropertiesT, typename... RestT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      detail::AreAllButLastReductions<RestT...>::value &&
          ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> parallel_for(nd_range<Dims> Range, PropertiesT Properties,
                          RestT &&...Rest) {
    constexpr detail::code_location CodeLoc = getCodeLocation<KernelName>();
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    using KernelType = std::decay_t<detail::nth_type_t<0, RestT...>>;

    // TODO The handler-less path does not support reductions, and
    // kernel functions with the kernel_handler type argument yet.
    if constexpr (sizeof...(RestT) == 1 &&
                  !(detail::KernelLambdaHasKernelHandlerArgT<
                      KernelType, sycl::nd_item<Dims>>::value)) {

      return detail::submit_kernel_direct_parallel_for<KernelName, true>(
          *this, Range, Rest..., {}, Properties, TlsCodeLocCapture.query());
    } else
      return submit(
          [&](handler &CGH) {
            CGH.template parallel_for<KernelName>(Range, Properties, Rest...);
          },
          TlsCodeLocCapture.query());
  }

  /// parallel_for version with a kernel represented as a lambda + nd_range that
  /// specifies global, local sizes and offset.
  ///
  /// \param Range specifies the global and local work spaces of the kernel
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, int Dims,
            typename... RestT>
  std::enable_if_t<detail::AreAllButLastReductions<RestT...>::value, event>
  parallel_for(nd_range<Dims> Range, RestT &&...Rest) {
    constexpr detail::code_location CodeLoc = getCodeLocation<KernelName>();
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    using KernelType = std::decay_t<detail::nth_type_t<0, RestT...>>;

    // TODO The handler-less path does not support reductions, and
    // kernel functions with the kernel_handler type argument yet.
    if constexpr (sizeof...(RestT) == 1 &&
                  !(detail::KernelLambdaHasKernelHandlerArgT<
                      KernelType, sycl::nd_item<Dims>>::value)) {
      return detail::submit_kernel_direct_parallel_for<KernelName, true>(
          *this, Range, Rest..., {},
          ext::oneapi::experimental::empty_properties_t{},
          TlsCodeLocCapture.query());
    } else {
      return submit(
          [&](handler &CGH) {
            CGH.template parallel_for<KernelName>(Range, Rest...);
          },
          TlsCodeLocCapture.query());
    }
  }

  /// parallel_for version with a kernel represented as a lambda + nd_range that
  /// specifies global, local sizes and offset.
  ///
  /// \param Range specifies the global and local work spaces of the kernel
  /// \param DepEvent is an event that specifies the kernel dependencies
  /// \param Properties is the kernel properties.
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, int Dims,
            typename PropertiesT, typename... RestT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      detail::AreAllButLastReductions<RestT...>::value &&
          ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> parallel_for(nd_range<Dims> Range, event DepEvent,
                          PropertiesT Properties, RestT &&...Rest) {
    constexpr detail::code_location CodeLoc = getCodeLocation<KernelName>();
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return submit(
        [&](handler &CGH) {
          CGH.depends_on(DepEvent);
          CGH.template parallel_for<KernelName>(Range, Properties, Rest...);
        },
        TlsCodeLocCapture.query());
  }

  /// parallel_for version with a kernel represented as a lambda + nd_range that
  /// specifies global, local sizes and offset.
  ///
  /// \param Range specifies the global and local work spaces of the kernel
  /// \param DepEvent is an event that specifies the kernel dependencies
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, int Dims,
            typename... RestT>
  std::enable_if_t<detail::AreAllButLastReductions<RestT...>::value, event>
  parallel_for(nd_range<Dims> Range, event DepEvent, RestT &&...Rest) {
    constexpr detail::code_location CodeLoc = getCodeLocation<KernelName>();
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    using KernelType = std::decay_t<detail::nth_type_t<0, RestT...>>;

    // TODO The handler-less path does not support reductions, and
    // kernel functions with the kernel_handler type argument yet.
    if constexpr (sizeof...(RestT) == 1 &&
                  !(detail::KernelLambdaHasKernelHandlerArgT<
                      KernelType, sycl::nd_item<Dims>>::value)) {
      return detail::submit_kernel_direct_parallel_for<KernelName, true>(
          *this, Range, Rest..., sycl::span<const event>(&DepEvent, 1),
          ext::oneapi::experimental::empty_properties_t{},
          TlsCodeLocCapture.query());
    } else {
      return submit(
          [&](handler &CGH) {
            CGH.depends_on(DepEvent);
            CGH.template parallel_for<KernelName>(Range, Rest...);
          },
          TlsCodeLocCapture.query());
    }
  }

  /// parallel_for version with a kernel represented as a lambda + nd_range that
  /// specifies global, local sizes and offset.
  ///
  /// \param Range specifies the global and local work spaces of the kernel
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies
  /// \param Properties is the kernel properties.
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, int Dims,
            typename PropertiesT, typename... RestT>
  __SYCL_DEPRECATED("To specify properties, use a launch configuration object "
                    "of type launch_config or a kernel functor with a "
                    "get(sycl::ext::oneapi::experimental::properties_tag) "
                    "member function instead.")
  std::enable_if_t<
      detail::AreAllButLastReductions<RestT...>::value &&
          ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event> parallel_for(nd_range<Dims> Range,
                          const std::vector<event> &DepEvents,
                          PropertiesT Properties, RestT &&...Rest) {
    constexpr detail::code_location CodeLoc = getCodeLocation<KernelName>();
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    return submit(
        [&](handler &CGH) {
          CGH.depends_on(DepEvents);
          CGH.template parallel_for<KernelName>(Range, Properties, Rest...);
        },
        TlsCodeLocCapture.query());
  }

  /// parallel_for version with a kernel represented as a lambda + nd_range that
  /// specifies global, local sizes and offset.
  ///
  /// \param Range specifies the global and local work spaces of the kernel
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies
  /// \param Rest acts as-if: "ReductionTypes&&... Reductions,
  /// const KernelType &KernelFunc".
  template <typename KernelName = detail::auto_name, int Dims,
            typename... RestT>
  std::enable_if_t<detail::AreAllButLastReductions<RestT...>::value, event>
  parallel_for(nd_range<Dims> Range, const std::vector<event> &DepEvents,
               RestT &&...Rest) {
    constexpr detail::code_location CodeLoc = getCodeLocation<KernelName>();
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    using KernelType = std::decay_t<detail::nth_type_t<0, RestT...>>;

    // TODO The handler-less path does not support reductions, and
    // kernel functions with the kernel_handler type argument yet.
    if constexpr (sizeof...(RestT) == 1 &&
                  !(detail::KernelLambdaHasKernelHandlerArgT<
                      KernelType, sycl::nd_item<Dims>>::value)) {
      return detail::submit_kernel_direct_parallel_for<KernelName, true>(
          *this, Range, Rest..., DepEvents,
          ext::oneapi::experimental::empty_properties_t{},
          TlsCodeLocCapture.query());
    } else {
      return submit(
          [&](handler &CGH) {
            CGH.depends_on(DepEvents);
            CGH.template parallel_for<KernelName>(Range, Rest...);
          },
          TlsCodeLocCapture.query());
    }
  }

  /// Copies data from a memory region pointed to by a placeholder accessor to
  /// another memory region pointed to by a shared_ptr.
  ///
  /// \param Src is a placeholder accessor to the source memory.
  /// \param Dest is a shared_ptr to the destination memory.
  /// \return an event representing copy operation.
  template <typename SrcT, int SrcDims, access_mode SrcMode, target SrcTgt,
            access::placeholder IsPlaceholder, typename DestT>
  event copy(
      accessor<SrcT, SrcDims, SrcMode, SrcTgt, IsPlaceholder> Src,
      std::shared_ptr<DestT> Dest,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return submit(
        [&](handler &CGH) {
          CGH.require(Src);
          CGH.copy(Src, Dest);
        },
        CodeLoc);
  }

  /// Copies data from a memory region pointed to by a shared_ptr to another
  /// memory region pointed to by a placeholder accessor.
  ///
  /// \param Src is a shared_ptr to the source memory.
  /// \param Dest is a placeholder accessor to the destination memory.
  /// \return an event representing copy operation.
  template <typename SrcT, typename DestT, int DestDims, access_mode DestMode,
            target DestTgt, access::placeholder IsPlaceholder>
  event copy(
      std::shared_ptr<SrcT> Src,
      accessor<DestT, DestDims, DestMode, DestTgt, IsPlaceholder> Dest,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return submit(
        [&](handler &CGH) {
          CGH.require(Dest);
          CGH.copy(Src, Dest);
        },
        CodeLoc);
  }

  /// Copies data from a memory region pointed to by a placeholder accessor to
  /// another memory region pointed to by a raw pointer.
  ///
  /// \param Src is a placeholder accessor to the source memory.
  /// \param Dest is a raw pointer to the destination memory.
  /// \return an event representing copy operation.
  template <typename SrcT, int SrcDims, access_mode SrcMode, target SrcTgt,
            access::placeholder IsPlaceholder, typename DestT>
  event copy(
      accessor<SrcT, SrcDims, SrcMode, SrcTgt, IsPlaceholder> Src, DestT *Dest,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return submit(
        [&](handler &CGH) {
          CGH.require(Src);
          CGH.copy(Src, Dest);
        },
        CodeLoc);
  }

  /// Copies data from a memory region pointed to by a raw pointer to another
  /// memory region pointed to by a placeholder accessor.
  ///
  /// \param Src is a raw pointer to the source memory.
  /// \param Dest is a placeholder accessor to the destination memory.
  /// \return an event representing copy operation.
  template <typename SrcT, typename DestT, int DestDims, access_mode DestMode,
            target DestTgt, access::placeholder IsPlaceholder>
  event copy(
      const SrcT *Src,
      accessor<DestT, DestDims, DestMode, DestTgt, IsPlaceholder> Dest,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return submit(
        [&](handler &CGH) {
          CGH.require(Dest);
          CGH.copy(Src, Dest);
        },
        CodeLoc);
  }

  /// Copies data from one memory region to another, both pointed by placeholder
  /// accessors.
  ///
  /// \param Src is a placeholder accessor to the source memory.
  /// \param Dest is a placeholder accessor to the destination memory.
  /// \return an event representing copy operation.
  template <typename SrcT, int SrcDims, access_mode SrcMode, target SrcTgt,
            access::placeholder IsSrcPlaceholder, typename DestT, int DestDims,
            access_mode DestMode, target DestTgt,
            access::placeholder IsDestPlaceholder>
  event copy(
      accessor<SrcT, SrcDims, SrcMode, SrcTgt, IsSrcPlaceholder> Src,
      accessor<DestT, DestDims, DestMode, DestTgt, IsDestPlaceholder> Dest,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return submit(
        [&](handler &CGH) {
          CGH.require(Src);
          CGH.require(Dest);
          CGH.copy(Src, Dest);
        },
        CodeLoc);
  }

  /// Provides guarantees that the memory object accessed via Acc is updated
  /// on the host after operation is complete.
  ///
  /// \param Acc is a SYCL accessor that needs to be updated on host.
  /// \return an event representing update_host operation.
  template <typename T, int Dims, access_mode Mode, target Tgt,
            access::placeholder IsPlaceholder>
  event update_host(
      accessor<T, Dims, Mode, Tgt, IsPlaceholder> Acc,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return submit(
        [&](handler &CGH) {
          CGH.require(Acc);
          CGH.update_host(Acc);
        },
        CodeLoc);
  }

  /// Fills the specified memory with the specified data.
  ///
  /// \param Dest is the placeholder accessor to the memory to fill.
  /// \param Src is the data to fill the memory with. T should be
  /// trivially copyable.
  /// \return an event representing fill operation.
  template <typename T, int Dims, access_mode Mode, target Tgt,
            access::placeholder IsPlaceholder>
  event fill(
      accessor<T, Dims, Mode, Tgt, IsPlaceholder> Dest, const T &Src,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return submit(
        [&](handler &CGH) {
          CGH.require(Dest);
          CGH.fill<T>(Dest, Src);
        },
        CodeLoc);
  }

  /// Shortcut for executing a graph of commands.
  ///
  /// \param Graph the graph of commands to execute
  /// \return an event representing graph execution operation.
  event ext_oneapi_graph(
      ext::oneapi::experimental::command_graph<
          ext::oneapi::experimental::graph_state::executable>
          Graph,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return submit_graph_direct_with_event_impl(*this, Graph, /*DepEvents*/ {},
                                               CodeLoc);
  }

  /// Shortcut for executing a graph of commands with a single dependency.
  ///
  /// \param Graph the graph of commands to execute
  /// \param DepEvent is an event that specifies the graph execution
  /// dependencies.
  /// \return an event representing graph execution operation.
  event ext_oneapi_graph(
      ext::oneapi::experimental::command_graph<
          ext::oneapi::experimental::graph_state::executable>
          Graph,
      event DepEvent,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return submit_graph_direct_with_event_impl(
        *this, Graph, sycl::span<const event>(&DepEvent, 1), CodeLoc);
  }

  /// Shortcut for executing a graph of commands with multiple dependencies.
  ///
  /// \param Graph the graph of commands to execute
  /// \param DepEvents is a vector of events that specifies the graph
  /// execution dependencies.
  /// \return an event representing graph execution operation.
  event ext_oneapi_graph(
      ext::oneapi::experimental::command_graph<
          ext::oneapi::experimental::graph_state::executable>
          Graph,
      const std::vector<event> &DepEvents,
      const detail::code_location &CodeLoc = detail::code_location::current()) {
    return submit_graph_direct_with_event_impl(*this, Graph, DepEvents,
                                               CodeLoc);
  }

  /// Provides a hint to the  runtime that previously issued commands to this
  /// queue should begin executing once their prerequisites have been satisfied.
  ///
  void ext_oneapi_prod();

  /// Returns whether the queue is in order or OoO
  ///
  /// Equivalent to has_property<property::queue::in_order>()
  bool is_in_order() const;

  /// Returns the backend associated with this queue.
  ///
  /// \return the backend associated with this queue.
  backend get_backend() const noexcept;

  /// Allows to check status of the queue (completed vs incomplete).
  ///
  /// \return returns true if all enqueued commands in the queue have been
  /// completed, otherwise returns false.
  bool ext_oneapi_empty() const;

  /// Allows to check status of the queue (completed vs incomplete).
  ///
  /// \return returns true if all enqueued commands in the queue have been
  /// completed, otherwise returns false.
#ifdef __DPCPP_ENABLE_UNFINISHED_KHR_EXTENSIONS
  bool khr_empty() const;
#endif

  std::optional<event> ext_oneapi_get_last_event() const {
    return static_cast<std::optional<event>>(ext_oneapi_get_last_event_impl());
  }

  void ext_oneapi_set_external_event(const event &external_event);

private:
  ur_native_handle_t getNative(int32_t &NativeHandleDesc) const;

  std::shared_ptr<detail::queue_impl> impl;
  queue(std::shared_ptr<detail::queue_impl> impl) : impl(impl) {}

  template <backend BackendName, class SyclObjectT>
  friend auto get_native(const SyclObjectT &Obj)
      -> backend_return_t<BackendName, SyclObjectT>;

  template <backend BackendName>
  friend auto get_native(const queue &Obj)
      -> backend_return_t<BackendName, queue>;

  template <typename CommandGroupFunc, typename PropertiesT>
  friend void ext::oneapi::experimental::detail::submit_impl(
      const queue &Q, PropertiesT Props, CommandGroupFunc &&CGF,
      const sycl::detail::code_location &CodeLoc);

  template <typename CommandGroupFunc, typename PropertiesT>
  friend event ext::oneapi::experimental::detail::submit_with_event_impl(
      const queue &Q, PropertiesT Props, CommandGroupFunc &&CGF,
      const sycl::detail::code_location &CodeLoc);

  template <typename PropertiesT>
  void ProcessSubmitProperties(PropertiesT Props,
                               detail::SubmissionInfo &SI) const {
    if constexpr (Props.template has_property<
                      ext::oneapi::experimental::event_mode_key>()) {
      ext::oneapi::experimental::event_mode EventModeProp =
          Props.template get_property<ext::oneapi::experimental::event_mode>();
      if (EventModeProp.value !=
          ext::oneapi::experimental::event_mode_enum::none)
        SI.EventMode() = EventModeProp.value;
    }
  }

  /// A template-free version of submit as const member function.
  event submit_with_event_impl(const detail::type_erased_cgfo_ty &CGH,
                               const detail::SubmissionInfo &SubmitInfo,
                               const detail::code_location &CodeLoc,
                               bool IsTopCodeLoc) const;

  /// A template-free version of submit_without_event as const member function.
  void submit_without_event_impl(const detail::type_erased_cgfo_ty &CGH,
                                 const detail::SubmissionInfo &SubmitInfo,
                                 const detail::code_location &CodeLoc,
                                 bool IsTopCodeLoc) const;

  /// Submits a command group function object to the queue, in order to be
  /// scheduled for execution on the device.
  ///
  /// \param Props is a property list with submission properties.
  /// \param CGF is a function object containing command group.
  /// \param CodeLoc is the code location of the submit call (default argument)
  /// \return a SYCL event object for the submitted command group.
  //
  template <typename PropertiesT>
  event submit_with_event(PropertiesT Props,
                          const detail::type_erased_cgfo_ty &CGF,
                          const detail::code_location &CodeLoc =
                              detail::code_location::current()) const {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    detail::SubmissionInfo SI{};
    ProcessSubmitProperties(Props, SI);
    return submit_with_event_impl(CGF, SI, TlsCodeLocCapture.query(),
                                  TlsCodeLocCapture.isToplevel());
  }

  /// Submits a command group function object to the queue, in order to be
  /// scheduled for execution on the device.
  ///
  /// \param Props is a property list with submission properties.
  /// \param CGF is a function object containing command group.
  /// \param CodeLoc is the code location of the submit call (default argument)
  //
  template <typename PropertiesT>
  void submit_without_event(PropertiesT Props,
                            const detail::type_erased_cgfo_ty &CGF,
                            const detail::code_location &CodeLoc) const {
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    detail::SubmissionInfo SI{};
    ProcessSubmitProperties(Props, SI);
    submit_without_event_impl(CGF, SI, TlsCodeLocCapture.query(),
                              TlsCodeLocCapture.isToplevel());
  }

  /// parallel_for_impl with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param Properties is the kernel properties.
  /// \param KernelFunc is the Kernel functor or lambda
  template <typename KernelName, int Dims, typename PropertiesT,
            typename... RestT>
  std::enable_if_t<
      detail::AreAllButLastReductions<RestT...>::value &&
          ext::oneapi::experimental::is_property_list<PropertiesT>::value,
      event>
  parallel_for_impl(range<Dims> Range, PropertiesT Properties,
                    RestT &&...Rest) {
    constexpr detail::code_location CodeLoc = getCodeLocation<KernelName>();
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    using KernelType =
        std::decay_t<detail::nth_type_t<sizeof...(RestT) - 1, RestT...>>;
    using LambdaArgType = sycl::detail::lambda_arg_type<KernelType, item<Dims>>;
    using TransformedArgType = std::conditional_t<
        std::is_integral<LambdaArgType>::value && Dims == 1, item<Dims>,
        typename detail::TransformUserItemType<Dims, LambdaArgType>::type>;

    // TODO The handler-less path does not support reductions, and
    // kernel functions with the kernel_handler type argument yet.
    if constexpr (sizeof...(RestT) == 1 &&
                  !(detail::KernelLambdaHasKernelHandlerArgT<
                      KernelType, TransformedArgType>::value)) {
      return detail::submit_kernel_direct_parallel_for<KernelName, true>(
          *this, Range, Rest..., {}, Properties, TlsCodeLocCapture.query());
    } else {
      return submit(
          [&](handler &CGH) {
            CGH.template parallel_for<KernelName>(Range, Properties, Rest...);
          },
          TlsCodeLocCapture.query());
    }
  }

  /// parallel_for_impl with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param KernelFunc is the Kernel functor or lambda
  template <typename KernelName, int Dims, typename... RestT>
  std::enable_if_t<detail::AreAllButLastReductions<RestT...>::value, event>
  parallel_for_impl(range<Dims> Range, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(
        Range, ext::oneapi::experimental::empty_properties_t{}, Rest...);
  }

  /// parallel_for_impl with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvent is an event that specifies the kernel dependencies
  /// \param Properties is the kernel properties.
  /// \param KernelFunc is the Kernel functor or lambda
  template <typename KernelName, int Dims, typename PropertiesT,
            typename... RestT>
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value, event>
  parallel_for_impl(range<Dims> Range, event DepEvent, PropertiesT Properties,
                    RestT &&...Rest) {
    constexpr detail::code_location CodeLoc = getCodeLocation<KernelName>();
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    using KernelType =
        std::decay_t<detail::nth_type_t<sizeof...(RestT) - 1, RestT...>>;
    using LambdaArgType = sycl::detail::lambda_arg_type<KernelType, item<Dims>>;
    using TransformedArgType = std::conditional_t<
        std::is_integral<LambdaArgType>::value && Dims == 1, item<Dims>,
        typename detail::TransformUserItemType<Dims, LambdaArgType>::type>;

    // TODO The handler-less path does not support reductions, and
    // kernel functions with the kernel_handler type argument yet.
    if constexpr (sizeof...(RestT) == 1 &&
                  !(detail::KernelLambdaHasKernelHandlerArgT<
                      KernelType, TransformedArgType>::value)) {
      return detail::submit_kernel_direct_parallel_for<KernelName, true>(
          *this, Range, Rest..., sycl::span<const event>(&DepEvent, 1),
          Properties, TlsCodeLocCapture.query());
    } else {
      return submit(
          [&](handler &CGH) {
            CGH.depends_on(DepEvent);
            CGH.template parallel_for<KernelName>(Range, Properties, Rest...);
          },
          TlsCodeLocCapture.query());
    }
  }

  /// parallel_for_impl with a kernel represented as a lambda + range that
  /// specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvent is an event that specifies the kernel dependencies
  /// \param KernelFunc is the Kernel functor or lambda
  template <typename KernelName, int Dims, typename... RestT>
  event parallel_for_impl(range<Dims> Range, event DepEvent, RestT &&...Rest) {
    return parallel_for_impl<KernelName>(
        Range, DepEvent, ext::oneapi::experimental::empty_properties_t{},
        Rest...);
  }

  /// parallel_for_impl version with a kernel represented as a lambda + range
  /// that specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies
  /// \param Properties is the kernel properties.
  /// \param KernelFunc is the Kernel functor or lambda
  template <typename KernelName, int Dims, typename PropertiesT,
            typename... RestT>
  std::enable_if_t<
      ext::oneapi::experimental::is_property_list<PropertiesT>::value, event>
  parallel_for_impl(range<Dims> Range, const std::vector<event> &DepEvents,
                    PropertiesT Properties, RestT &&...Rest) {
    constexpr detail::code_location CodeLoc = getCodeLocation<KernelName>();
    detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);
    using KernelType =
        std::decay_t<detail::nth_type_t<sizeof...(RestT) - 1, RestT...>>;
    using LambdaArgType = sycl::detail::lambda_arg_type<KernelType, item<Dims>>;
    using TransformedArgType = std::conditional_t<
        std::is_integral<LambdaArgType>::value && Dims == 1, item<Dims>,
        typename detail::TransformUserItemType<Dims, LambdaArgType>::type>;

    // TODO The handler-less path does not support reductions, and
    // kernel functions with the kernel_handler type argument yet.
    if constexpr (sizeof...(RestT) == 1 &&
                  !(detail::KernelLambdaHasKernelHandlerArgT<
                      KernelType, TransformedArgType>::value)) {
      return detail::submit_kernel_direct_parallel_for<KernelName, true>(
          *this, Range, Rest..., DepEvents, Properties,
          TlsCodeLocCapture.query());
    } else {
      return submit(
          [&](handler &CGH) {
            CGH.depends_on(DepEvents);
            CGH.template parallel_for<KernelName>(Range, Properties, Rest...);
          },
          TlsCodeLocCapture.query());
    }
  }

  /// parallel_for_impl version with a kernel represented as a lambda + range
  /// that specifies global size only.
  ///
  /// \param Range specifies the global work space of the kernel
  /// \param DepEvents is a vector of events that specifies the kernel
  /// dependencies
  /// \param KernelFunc is the Kernel functor or lambda
  template <typename KernelName, int Dims, typename... RestT>
  event parallel_for_impl(range<Dims> Range,
                          const std::vector<event> &DepEvents,
                          RestT &&...Rest) {
    return parallel_for_impl<KernelName>(
        Range, DepEvents, ext::oneapi::experimental::empty_properties_t{},
        Rest...);
  }

  event memcpyToDeviceGlobal(void *DeviceGlobalPtr, const void *Src,
                             bool IsDeviceImageScope, size_t NumBytes,
                             size_t Offset,
                             const std::vector<event> &DepEvents);
  event memcpyFromDeviceGlobal(void *Dest, const void *DeviceGlobalPtr,
                               bool IsDeviceImageScope, size_t NumBytes,
                               size_t Offset,
                               const std::vector<event> &DepEvents);
  const property_list &getPropList() const;

  // Helper implementation for ext_oneapi_get_last_event. This is needed to
  // avoid issues where std::optional has a different layout between user-code
  // and library.
  sycl::detail::optional<event> ext_oneapi_get_last_event_impl() const;

  template <typename KernelName>
  static constexpr detail::code_location getCodeLocation() {
    constexpr auto Info = detail::CompileTimeKernelInfo<KernelName>;
    return {Info.FileName.data(), Info.FunctionName.data(), Info.LineNumber,
            Info.ColumnNumber};
  }
};

namespace detail {

template <detail::WrapAs WrapAs, typename LambdaArgType, typename KernelName,
          bool EventNeeded, typename PropertiesT,
          typename KernelTypeUniversalRef, int Dims>
auto submit_kernel_direct(const queue &Queue,
                          const detail::nd_range_view &RangeView,
                          KernelTypeUniversalRef &&KernelFunc,
                          sycl::span<const event> DepEvents,
                          const PropertiesT &ExtraProps,
                          const detail::code_location &CodeLoc) {
  detail::tls_code_loc_t TlsCodeLocCapture(CodeLoc);

  using KernelType = std::decay_t<KernelTypeUniversalRef>;

  detail::KernelWrapper<WrapAs, KernelName, KernelType, LambdaArgType,
                        PropertiesT>::wrap(KernelFunc);

  // Instantiating the kernel on the host improves debugging.
  // Passing this pointer to another translation unit prevents optimization.
#ifndef NDEBUG
  // TODO: call library to prevent dropping call due to optimization.
  (void)
      detail::GetInstantiateKernelOnHostPtr<KernelType, LambdaArgType, Dims>();
#endif

  detail::DeviceKernelInfo *DeviceKernelInfoPtr =
      &detail::getDeviceKernelInfo<KernelName>();
  constexpr auto Info = detail::CompileTimeKernelInfo<KernelName>;

  assert(Info.Name != std::string_view{} && "Kernel must have a name!");

  static_assert(
      Info.Name == std::string_view{} || sizeof(KernelType) == Info.KernelSize,
      "Unexpected kernel lambda size. This can be caused by an "
      "external host compiler producing a lambda with an "
      "unexpected layout. This is a limitation of the compiler."
      "In many cases the difference is related to capturing constexpr "
      "variables. In such cases removing constexpr specifier aligns the "
      "captures between the host compiler and the device compiler."
      "\n"
      "In case of MSVC, passing "
      "-fsycl-host-compiler-options='/std:c++latest' "
      "might also help.");

  auto ParsedProperties = extractKernelProperties([&]() {
    if constexpr (ext::oneapi::experimental::detail::
                      HasKernelPropertiesGetMethod<const KernelType &>::value) {
      // Merge properties via get() and manually specified properties.
      // get() method is used for specifying kernel properties but properties
      // passed via launch_config (ExtraProps) should be kernel launch
      // properties. They are mutually exclusive, so there should not be any
      // conflict when merging properties. merge_properties() throws if there's
      // a conflict.
      return sycl::ext::oneapi::experimental::detail::merge_properties(
          ExtraProps,
          KernelFunc.get(ext::oneapi::experimental::properties_tag{}));
    } else {
      return ExtraProps;
    }
  }());

  HostKernelRef<KernelType, KernelTypeUniversalRef, LambdaArgType, Dims>
      HostKernel(std::forward<KernelTypeUniversalRef>(KernelFunc));

  if constexpr (EventNeeded) {
    return submit_kernel_direct_with_event_impl(
        Queue, RangeView, HostKernel, DeviceKernelInfoPtr, DepEvents,
        ParsedProperties, TlsCodeLocCapture.query(),
        TlsCodeLocCapture.isToplevel());
  } else {
    submit_kernel_direct_without_event_impl(
        Queue, RangeView, HostKernel, DeviceKernelInfoPtr, DepEvents,
        ParsedProperties, TlsCodeLocCapture.query(),
        TlsCodeLocCapture.isToplevel());
  }
}

template <typename KernelName, bool EventNeeded, typename PropertiesT,
          typename KernelTypeUniversalRef, int Dims>
auto submit_kernel_direct_parallel_for(const queue &Queue, nd_range<Dims> Range,
                                       KernelTypeUniversalRef &&KernelFunc,
                                       sycl::span<const event> DepEvents,
                                       const PropertiesT &Props,
                                       const detail::code_location &CodeLoc) {

  using KernelType = std::decay_t<KernelTypeUniversalRef>;
  using NameT =
      typename detail::get_kernel_name_t<KernelName, KernelType>::name;

  using LambdaArgType =
      sycl::detail::lambda_arg_type<KernelType, nd_item<Dims>>;
  static_assert(
      std::is_convertible_v<sycl::nd_item<Dims>, LambdaArgType>,
      "Kernel argument of a sycl::parallel_for with sycl::nd_range "
      "must be either sycl::nd_item or be convertible from sycl::nd_item");
  using TransformedArgType = sycl::nd_item<Dims>;

#ifndef __SYCL_DEVICE_ONLY__
  detail::checkValueRange<Dims>(Range);
#endif

  return submit_kernel_direct<detail::WrapAs::parallel_for, TransformedArgType,
                              NameT, EventNeeded, PropertiesT,
                              KernelTypeUniversalRef, Dims>(
      Queue, detail::nd_range_view(Range),
      std::forward<KernelTypeUniversalRef>(KernelFunc), DepEvents, Props,
      CodeLoc);
}

template <typename KernelName, bool EventNeeded, typename PropertiesT,
          typename KernelTypeUniversalRef, int Dims>
auto submit_kernel_direct_parallel_for(const queue &Queue, range<Dims> Range,
                                       KernelTypeUniversalRef &&KernelFunc,
                                       sycl::span<const event> DepEvents,
                                       const PropertiesT &Props,
                                       const detail::code_location &CodeLoc) {

#ifndef __SYCL_DEVICE_ONLY__
  if (!range_size_fits_in_size_t(Range))
    throw sycl::exception(make_error_code(errc::runtime),
                          "The total number of work-items in "
                          "a range must fit within size_t");
#endif

  using KernelType = std::decay_t<KernelTypeUniversalRef>;
  using NameT =
      typename detail::get_kernel_name_t<KernelName, KernelType>::name;
  using LambdaArgType = sycl::detail::lambda_arg_type<KernelType, item<Dims>>;

  // If 1D kernel argument is an integral type, convert it to sycl::item<1>
  // If user type is convertible from sycl::item/sycl::nd_item, use
  // sycl::item/sycl::nd_item to transport item information
  using TransformedArgType = std::conditional_t<
      std::is_integral<LambdaArgType>::value && Dims == 1, item<Dims>,
      typename detail::TransformUserItemType<Dims, LambdaArgType>::type>;

  static_assert(!std::is_same_v<TransformedArgType, sycl::nd_item<Dims>>,
                "Kernel argument cannot have a sycl::nd_item type in "
                "sycl::parallel_for with sycl::range");

  static_assert(std::is_convertible_v<item<Dims>, LambdaArgType> ||
                    std::is_convertible_v<item<Dims, false>, LambdaArgType>,
                "sycl::parallel_for(sycl::range) kernel must have the "
                "first argument of sycl::item type, or of a type which is "
                "implicitly convertible from sycl::item");

  using RefLambdaArgType = std::add_lvalue_reference_t<LambdaArgType>;
  static_assert(
      (std::is_invocable_v<KernelType, RefLambdaArgType>),
      "SYCL kernel lambda/functor has an unexpected signature, it should be "
      "invocable with sycl::item");

  // Range rounding can be disabled by the user.
  // Range rounding is supported only for newer SYCL standards.
#if !defined(__SYCL_DISABLE_PARALLEL_FOR_RANGE_ROUNDING__) &&                  \
    SYCL_LANGUAGE_VERSION >= 202012L
  auto [RoundedRange, HasRoundedRange] =
      detail::getRoundedRange(Range, Queue.get_device());
  if (HasRoundedRange) {
    using NameWT = typename detail::get_kernel_wrapper_name_t<NameT>::name;
    auto Wrapper =
        detail::getRangeRoundedKernelLambda<NameWT, TransformedArgType, Dims>(
            KernelFunc, Range);

    using KTypeWrapper = decltype(Wrapper);
    using KName = std::conditional_t<std::is_same<KernelType, NameT>::value,
                                     KTypeWrapper, NameWT>;
#ifndef __SYCL_DEVICE_ONLY__
    // We are executing over the rounded range, but there are still
    // items/ids that are constructed in the range rounded
    // kernel, use items/ids in the user range, which means that
    // __SYCL_ASSUME_INT can still be violated. So check the bounds
    // of the user range, instead of the rounded range.
    detail::checkValueRange<Dims>(Range);
#endif
    return submit_kernel_direct<detail::WrapAs::parallel_for,
                                TransformedArgType, KName, EventNeeded,
                                PropertiesT, KTypeWrapper, Dims>(
        Queue, detail::nd_range_view(RoundedRange), std::move(Wrapper),
        DepEvents, Props, CodeLoc);
  } else
#endif // !__SYCL_DISABLE_PARALLEL_FOR_RANGE_ROUNDING__ &&
       // SYCL_LANGUAGE_VERSION >= 202012L
  {
#ifndef __SYCL_FORCE_PARALLEL_FOR_RANGE_ROUNDING__
#ifndef __SYCL_DEVICE_ONLY__
    detail::checkValueRange<Dims>(Range);
#endif
    return submit_kernel_direct<detail::WrapAs::parallel_for,
                                TransformedArgType, NameT, EventNeeded,
                                PropertiesT, KernelTypeUniversalRef, Dims>(
        Queue, detail::nd_range_view(Range),
        std::forward<KernelTypeUniversalRef>(KernelFunc), DepEvents, Props,
        CodeLoc);

#else
    (void)Range;
    (void)Props;
    (void)KernelFunc;
#endif // __SYCL_FORCE_PARALLEL_FOR_RANGE_ROUNDING__
  }
}

template <typename KernelName, bool EventNeeded, typename PropertiesT,
          typename KernelTypeUniversalRef>
auto submit_kernel_direct_single_task(const queue &Queue,
                                      KernelTypeUniversalRef &&KernelFunc,
                                      sycl::span<const event> DepEvents,
                                      const PropertiesT &Props,
                                      const detail::code_location &CodeLoc) {

  using KernelType = std::decay_t<KernelTypeUniversalRef>;
  using NameT =
      typename detail::get_kernel_name_t<KernelName, KernelType>::name;

  return submit_kernel_direct<detail::WrapAs::single_task, void, NameT,
                              EventNeeded, PropertiesT, KernelTypeUniversalRef,
                              1>(
      Queue, detail::nd_range_view(),
      std::forward<KernelTypeUniversalRef>(KernelFunc), DepEvents, Props,
      CodeLoc);
}
} // namespace detail

} // namespace _V1
} // namespace sycl

namespace std {
template <> struct __SYCL_EXPORT hash<sycl::queue> {
  size_t operator()(const sycl::queue &Q) const;
};
} // namespace std
