[llvm] [SYCL] Add sycl::device initial implementation (PR #176972)

Kseniya Tikhomirova via llvm-commits llvm-commits at lists.llvm.org
Tue Jan 20 09:20:50 PST 2026


https://github.com/KseniyaTikhomirova created https://github.com/llvm/llvm-project/pull/176972

This is part of the SYCL support upstreaming effort. The relevant RFCs can be found here:

https://discourse.llvm.org/t/rfc-add-full-support-for-the-sycl-programming-model/74080 https://discourse.llvm.org/t/rfc-sycl-runtime-upstreaming/74479

Plan for next PR:

E2E lit configs & test for get_platforms & get_devices impl
context & USM free functions impl

>From 372e12252f621ea8b20868fb6102f67f3fd4ff3e Mon Sep 17 00:00:00 2001
From: "Tikhomirova, Kseniya" <kseniya.tikhomirova at intel.com>
Date: Wed, 19 Nov 2025 10:15:32 -0800
Subject: [PATCH] [SYCL] Add sycl::device initial implementation

Signed-off-by: Tikhomirova, Kseniya <kseniya.tikhomirova at intel.com>
---
 libsycl/docs/index.rst                        |  25 ++-
 libsycl/include/sycl/__impl/aspect.hpp        |  43 +++++
 libsycl/include/sycl/__impl/device.hpp        | 182 ++++++++++++++++++
 .../include/sycl/__impl/device_selector.hpp   | 122 ++++++++++++
 libsycl/include/sycl/__impl/info/device.hpp   |  80 ++++++++
 .../include/sycl/__impl/info/device_type.hpp  |  35 ++++
 libsycl/include/sycl/__impl/platform.hpp      |  24 +++
 libsycl/include/sycl/sycl.hpp                 |   2 +
 libsycl/src/CMakeLists.txt                    |   3 +
 libsycl/src/detail/device_impl.cpp            |  55 ++++++
 libsycl/src/detail/device_impl.hpp            | 122 ++++++++++++
 libsycl/src/detail/global_objects.cpp         |   3 +-
 .../src/detail/offload/offload_topology.cpp   |  65 +++++--
 .../src/detail/offload/offload_topology.hpp   |  64 ++----
 libsycl/src/detail/offload/offload_utils.cpp  |  31 ++-
 libsycl/src/detail/offload/offload_utils.hpp  |  15 ++
 libsycl/src/detail/platform_impl.cpp          |  64 +++++-
 libsycl/src/detail/platform_impl.hpp          |  26 ++-
 libsycl/src/device.cpp                        | 104 ++++++++++
 libsycl/src/device_selector.cpp               | 123 ++++++++++++
 libsycl/src/platform.cpp                      |  19 +-
 libsycl/tools/sycl-ls/sycl-ls.cpp             |  88 ++++++++-
 22 files changed, 1214 insertions(+), 81 deletions(-)
 create mode 100644 libsycl/include/sycl/__impl/aspect.hpp
 create mode 100644 libsycl/include/sycl/__impl/device.hpp
 create mode 100644 libsycl/include/sycl/__impl/device_selector.hpp
 create mode 100644 libsycl/include/sycl/__impl/info/device.hpp
 create mode 100644 libsycl/include/sycl/__impl/info/device_type.hpp
 create mode 100644 libsycl/src/detail/device_impl.cpp
 create mode 100644 libsycl/src/detail/device_impl.hpp
 create mode 100644 libsycl/src/device.cpp
 create mode 100644 libsycl/src/device_selector.cpp

diff --git a/libsycl/docs/index.rst b/libsycl/docs/index.rst
index ce48743be3ae2..03e083227ace4 100644
--- a/libsycl/docs/index.rst
+++ b/libsycl/docs/index.rst
@@ -1,6 +1,6 @@
-=====================
+===========================
 SYCL runtime implementation
-=====================
+===========================
 
 .. contents::
    :local:
@@ -8,7 +8,7 @@ SYCL runtime implementation
 .. _index:
 
 Current Status
-========
+==============
 
 The implementation is in the very early stages of upstreaming. The first
 milestone is to get
@@ -59,7 +59,7 @@ libsycl side:
   from the multi-architectural binaries
 
 Build steps
-========
+===========
 
 To build LLVM with libsycl runtime enabled the following script can be used.
 
@@ -87,7 +87,22 @@ To build LLVM with libsycl runtime enabled the following script can be used.
 
 
 Limitations
-========
+===========
 
 Libsycl is not currently supported on Windows because it depends on liboffload
 which doesn't currently support Windows.
+
+TODO for added SYCL classes
+===========================
+
+* ``exception``: methods with context are not implemented, to add once context is ready
+* ``platform``: deprecated info descriptor is not implemented (info::platform::extensions), to implement on RT level with ``device::get_info<info::device::aspects>()``
+* ``device``:
+
+  * ``get_info``: to find an efficient way to map descriptors to liboffload types, add other descriptors, add cache of info data
+  * ``has(aspect)``: same as get_info
+  * ``create_sub_devices``: partitioning is not supported by liboffload now, blocked
+  * ``has_extension``: deprecated API, to implement on RT level with ``device::has``
+
+* device selection: to add compatibility with old SYCL 1.2.1 device selectors, still part of SYCL 2020 specification
+
diff --git a/libsycl/include/sycl/__impl/aspect.hpp b/libsycl/include/sycl/__impl/aspect.hpp
new file mode 100644
index 0000000000000..0a73dd621df9a
--- /dev/null
+++ b/libsycl/include/sycl/__impl/aspect.hpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBSYCL___IMPL_ASPECT_HPP
+#define _LIBSYCL___IMPL_ASPECT_HPP
+
+#include <sycl/__impl/detail/config.hpp>
+
+#include <cstdint>
+
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+
+// SYCL 2020 4.6.4.5. Aspects.
+enum class aspect : std::uint32_t {
+  cpu,
+  gpu,
+  accelerator,
+  custom,
+  emulated,
+  host_debuggable,
+  fp16,
+  fp64,
+  atomic64,
+  image,
+  online_compiler,
+  online_linker,
+  queue_profiling,
+  usm_device_allocations,
+  usm_host_allocations,
+  usm_atomic_host_allocations,
+  usm_shared_allocations,
+  usm_atomic_shared_allocations,
+  usm_system_allocations
+};
+
+_LIBSYCL_END_NAMESPACE_SYCL
+
+#endif // _LIBSYCL___IMPL_ASPECT_HPP
diff --git a/libsycl/include/sycl/__impl/device.hpp b/libsycl/include/sycl/__impl/device.hpp
new file mode 100644
index 0000000000000..84f7e20175f6d
--- /dev/null
+++ b/libsycl/include/sycl/__impl/device.hpp
@@ -0,0 +1,182 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains the declaration of the SYCL 2020 device class, which
+/// represents a single SYCL device on which kernels can be executed.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBSYCL___IMPL_DEVICE_HPP
+#define _LIBSYCL___IMPL_DEVICE_HPP
+
+#include <sycl/__impl/aspect.hpp>
+#include <sycl/__impl/backend.hpp>
+#include <sycl/__impl/device_selector.hpp>
+#include <sycl/__impl/info/device.hpp>
+
+#include <sycl/__impl/detail/config.hpp>
+#include <sycl/__impl/detail/obj_utils.hpp>
+
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+
+class platform;
+
+namespace detail {
+class DeviceImpl;
+} // namespace detail
+
+// SYCL 2020 4.6.4. Device class.
+class _LIBSYCL_EXPORT device {
+public:
+  device(const device &rhs) = default;
+
+  device(device &&rhs) = default;
+
+  device &operator=(const device &rhs) = default;
+
+  device &operator=(device &&rhs) = default;
+
+  friend bool operator==(const device &lhs, const device &rhs) {
+    return lhs.impl == rhs.impl;
+  }
+
+  friend bool operator!=(const device &lhs, const device &rhs) {
+    return !(lhs == rhs);
+  }
+
+  /// Constructs a SYCL device instance using the default device (device chosen
+  /// by default device selector).
+  device();
+
+  /// Constructs a SYCL device 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.
+  template <
+      typename DeviceSelector,
+      // `DeviceImpl` (used as a parameter in private ctor) is incomplete
+      // so would result in a error trying to instantiate
+      // `EnableIfDeviceSelectorIsInvocable` below. Filter it out
+      // before trying to do that.
+      typename =
+          std::enable_if_t<!std::is_same_v<DeviceSelector, detail::DeviceImpl>>,
+      typename = detail::EnableIfDeviceSelectorIsInvocable<DeviceSelector>>
+  explicit device(const DeviceSelector &deviceSelector)
+      : device(detail::SelectDevice(deviceSelector)) {}
+
+  /// Returns the backend associated with this device.
+  ///
+  /// \return the backend associated with this device.
+  backend get_backend() const noexcept;
+
+  /// Check if device is a CPU device.
+  ///
+  /// \return true if SYCL device is a CPU device.
+  bool is_cpu() const;
+
+  /// Check if device is a GPU device.
+  ///
+  /// \return true if SYCL device is a GPU device.
+  bool is_gpu() const;
+
+  /// Check if device is an accelerator device.
+  ///
+  /// \return true if SYCL device is an accelerator device.
+  bool is_accelerator() const;
+
+  /// Get associated SYCL platform.
+  ///
+  /// \return The associated SYCL platform.
+  platform get_platform() const;
+
+  /// Queries this SYCL device for information requested by the template
+  /// parameter param.
+  ///
+  /// \return device info of type described in 4.6.4.4. Information descriptors.
+  template <typename Param>
+  detail::is_device_info_desc_t<Param> get_info() const;
+
+  /// Queries this SYCL device 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;
+
+  /// Queries which optional features this device supports (if any).
+  ///
+  /// \return true if this device has the given aspect.
+  bool has(aspect asp) const;
+
+  /// Partition device into sub devices
+  ///
+  /// Available only when prop is info::partition_property::partition_equally.
+  /// If this SYCL device does not support
+  /// info::partition_property::partition_equally a feature_not_supported
+  /// exception must be thrown.
+  ///
+  /// \param ComputeUnits is a desired count of compute units in each sub
+  /// device.
+  /// \return A vector class of sub devices partitioned from this SYCL
+  /// device equally based on the ComputeUnits parameter.
+  template <info::partition_property prop>
+  std::vector<device> create_sub_devices(size_t ComputeUnits) const;
+
+  /// Partition device into sub devices.
+  ///
+  /// Available only when prop is info::partition_property::partition_by_counts.
+  /// If this SYCL device does not support
+  /// info::partition_property::partition_by_counts a feature_not_supported
+  /// exception must be thrown.
+  ///
+  /// \param Counts is a std::vector of desired compute units in sub devices.
+  /// \return a std::vector of sub devices partitioned from this SYCL device by
+  /// count sizes based on the Counts parameter.
+  template <info::partition_property prop>
+  std::vector<device>
+  create_sub_devices(const std::vector<size_t> &Counts) const;
+
+  /// Partition device into sub devices.
+  ///
+  /// Available only when prop is
+  /// info::partition_property::partition_by_affinity_domain. If this SYCL
+  /// device does not support
+  /// info::partition_property::partition_by_affinity_domain or the SYCL device
+  /// does not support info::affinity_domain provided a feature_not_supported
+  /// exception must be thrown.
+  ///
+  /// \param AffinityDomain is one of the values described in Table 4.20 of SYCL
+  /// Spec.
+  /// \return a vector class of sub devices partitioned from this SYCL
+  /// device by affinity domain based on the AffinityDomain parameter.
+  template <info::partition_property prop>
+  std::vector<device>
+  create_sub_devices(info::partition_affinity_domain AffinityDomain) const;
+
+  /// Query available SYCL devices.
+  ///
+  /// \param deviceType is one of the values described in A.3 of SYCL Spec.
+  /// \return a std::vector containing all SYCL devices available in the system
+  /// of the device type specified.
+  static std::vector<device>
+  get_devices(info::device_type deviceType = info::device_type::all);
+
+private:
+  device(detail::DeviceImpl &Impl) : impl(&Impl) {}
+  detail::DeviceImpl *impl;
+
+  friend sycl::detail::ImplUtils;
+}; // class device
+
+_LIBSYCL_END_NAMESPACE_SYCL
+
+template <>
+struct std::hash<sycl::device> : public sycl::detail::HashBase<sycl::device> {};
+
+#endif // _LIBSYCL___IMPL_DEVICE_HPP
diff --git a/libsycl/include/sycl/__impl/device_selector.hpp b/libsycl/include/sycl/__impl/device_selector.hpp
new file mode 100644
index 0000000000000..2f118367fc1e0
--- /dev/null
+++ b/libsycl/include/sycl/__impl/device_selector.hpp
@@ -0,0 +1,122 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains the declaration of the standard device selectors
+/// (SYCL 2020 4.6.1.1. Device selector) included with all SYCL implementations.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBSYCL___IMPL_DEVICE_SELECTOR_HPP
+#define _LIBSYCL___IMPL_DEVICE_SELECTOR_HPP
+
+#include <sycl/__impl/aspect.hpp>
+#include <sycl/__impl/detail/config.hpp>
+
+#include <functional>
+
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+
+class device;
+
+namespace detail {
+
+// SYCL 2020 4.6.1.1. Device selector:
+// The interface for a device selector is any object that meets the C++ named
+// requirement Callable, taking a parameter of type const device & and returning
+// a value that is implicitly convertible to int.
+using DeviceSelectorInvocableType = std::function<int(const sycl::device &)>;
+
+template <typename DeviceSelector>
+using EnableIfDeviceSelectorIsInvocable = std::enable_if_t<
+    std::is_invocable_r_v<int, DeviceSelector &, const device &>>;
+
+/// Returns a SYCL device instance chosen by the device selector provided.
+///
+/// \param DeviceSelector is SYCL 2020 Device Selector, a simple callable that
+/// takes a device and returns an int.
+/// \return device chosen by selector.
+_LIBSYCL_EXPORT device
+SelectDevice(const DeviceSelectorInvocableType &DeviceSelector);
+
+} // namespace detail
+
+/// Standard device selector to select SYCL device from any supported SYCL
+/// backend based on an implementation-defined heuristic.
+///
+/// \param Dev SYCL 2020 device to calculate the score for.
+/// \return score value for the provided device. Further device selection is
+/// based on score values.
+_LIBSYCL_EXPORT int default_selector_v(const device &Dev);
+
+/// Standard device selector to select SYCL device from any supported SYCL
+/// backend for which the device type is info::device_type::gpu.
+///
+/// \param Dev SYCL 2020 device to calculate the score for.
+/// \return score value for the provided device. Further device selection is
+/// based on score values.
+_LIBSYCL_EXPORT int gpu_selector_v(const device &Dev);
+
+/// Standard device selector to select SYCL device from any supported SYCL
+/// backend for which the device type is info::device_type::cpu.
+///
+/// \param Dev SYCL 2020 device to calculate the score for.
+/// \return score value for the provided device. Further device selection is
+/// based on score values.
+_LIBSYCL_EXPORT int cpu_selector_v(const device &Dev);
+
+/// Standard device selector to select SYCL device from any supported SYCL
+/// backend for which the device type is info::device_type::accelerator.
+///
+/// \param Dev SYCL 2020 device to calculate the score for.
+/// \return score value for the provided device. Further device selection is
+/// based on score values.
+_LIBSYCL_EXPORT int accelerator_selector_v(const device &Dev);
+
+/// Returns a selector object that selects a SYCL device from any supported SYCL
+/// backend which contains all the requested aspects.
+///
+/// \param RequireList requested aspects,  i.e. for the specific device dev and
+/// each aspect devAspect from RequireList dev.has(devAspect) equals true.
+/// \param DenyList all the aspects that have to be avoided, i.e. for the
+/// specific device dev and each aspect devAspect from denyList
+/// dev.has(devAspect) equals false.
+/// \return a selector object
+_LIBSYCL_EXPORT detail::DeviceSelectorInvocableType
+aspect_selector(const std::vector<aspect> &RequireList,
+                const std::vector<aspect> &DenyList = {});
+
+/// Returns a selector object that selects a SYCL device from any supported SYCL
+/// backend which contains all the requested aspects.
+///
+/// \param AspectList requested aspects,  i.e. for the specific device dev and
+/// each aspect devAspect from AspectList dev.has(devAspect) equals true.
+/// \return a selector object
+template <typename... AspectListT>
+detail::DeviceSelectorInvocableType aspect_selector(AspectListT... AspectList) {
+  std::vector<aspect> RequireList;
+  RequireList.reserve(sizeof...(AspectList));
+  (RequireList.emplace_back(AspectList), ...);
+
+  return aspect_selector(RequireList, {});
+}
+
+/// Returns a selector object that selects a SYCL device from any supported SYCL
+/// backend which contains all the requested aspects.
+///
+/// \param AspectList requested aspects,  i.e. for the specific device dev and
+/// each aspect devAspect from AspectList dev.has(devAspect) equals true.
+/// \return a selector object
+template <aspect... AspectList>
+detail::DeviceSelectorInvocableType aspect_selector() {
+  return aspect_selector({AspectList...}, {});
+}
+
+_LIBSYCL_END_NAMESPACE_SYCL
+
+#endif //_LIBSYCL___IMPL_DEVICE_SELECTOR_HPP
diff --git a/libsycl/include/sycl/__impl/info/device.hpp b/libsycl/include/sycl/__impl/info/device.hpp
new file mode 100644
index 0000000000000..d5eee410275bb
--- /dev/null
+++ b/libsycl/include/sycl/__impl/info/device.hpp
@@ -0,0 +1,80 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains the declaration of SYCL 2020 device info types.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBSYCL___IMPL_INFO_DEVICE_HPP
+#define _LIBSYCL___IMPL_INFO_DEVICE_HPP
+
+#include <sycl/__impl/aspect.hpp>
+#include <sycl/__impl/detail/config.hpp>
+#include <sycl/__impl/info/desc_base.hpp>
+#include <sycl/__impl/info/device_type.hpp>
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+
+class device;
+class platform;
+
+namespace detail {
+template <typename T>
+using is_device_info_desc_t = typename is_info_desc<T, device>::return_type;
+} // namespace detail
+
+// SYCL 2020 A.3. Device information descriptors.
+namespace info {
+
+enum class partition_property : std::uint32_t {
+  no_partition = 0,
+  partition_equally,
+  partition_by_counts,
+  partition_by_affinity_domain
+};
+
+enum class partition_affinity_domain : std::uint32_t {
+  not_applicable = 0,
+  numa,
+  L4_cache,
+  L3_cache,
+  L2_cache,
+  L1_cache,
+  next_partitionable
+};
+
+namespace device {
+// SYCL 2020 4.6.4.4. Information descriptors.
+
+struct device_type : detail::info_desc_tag<device_type, sycl::device> {
+  using return_type = sycl::info::device_type;
+};
+struct name : detail::info_desc_tag<name, sycl::device> {
+  using return_type = std::string;
+};
+struct vendor : detail::info_desc_tag<vendor, sycl::device> {
+  using return_type = std::string;
+};
+struct driver_version : detail::info_desc_tag<driver_version, sycl::device> {
+  using return_type = std::string;
+};
+struct platform : detail::info_desc_tag<platform, sycl::device> {
+  using return_type = sycl::platform;
+};
+
+} // namespace device
+} // namespace info
+
+_LIBSYCL_END_NAMESPACE_SYCL
+
+#endif // _LIBSYCL___IMPL_INFO_DEVICE_HPP
diff --git a/libsycl/include/sycl/__impl/info/device_type.hpp b/libsycl/include/sycl/__impl/info/device_type.hpp
new file mode 100644
index 0000000000000..90db655063859
--- /dev/null
+++ b/libsycl/include/sycl/__impl/info/device_type.hpp
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBSYCL___IMPL_INFO_DEVICE_TYPE_HPP
+#define _LIBSYCL___IMPL_INFO_DEVICE_TYPE_HPP
+
+#include <sycl/__impl/detail/config.hpp>
+
+#include <cstdint>
+
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+
+namespace info {
+
+// SYCL 2020 4.6.4.7.1. Device type.
+enum class device_type : std::uint32_t {
+  cpu = 0,
+  gpu,
+  accelerator,
+  custom,
+  automatic,
+  host, // Deprecated by SYCL 2020
+  all
+};
+
+} // namespace info
+
+_LIBSYCL_END_NAMESPACE_SYCL
+
+#endif // _LIBSYCL___IMPL_INFO_DEVICE_TYPE_HPP
diff --git a/libsycl/include/sycl/__impl/platform.hpp b/libsycl/include/sycl/__impl/platform.hpp
index c7915eaa224fe..7566eec89bc9e 100644
--- a/libsycl/include/sycl/__impl/platform.hpp
+++ b/libsycl/include/sycl/__impl/platform.hpp
@@ -15,9 +15,11 @@
 #ifndef _LIBSYCL___IMPL_PLATFORM_HPP
 #define _LIBSYCL___IMPL_PLATFORM_HPP
 
+#include <sycl/__impl/aspect.hpp>
 #include <sycl/__impl/backend.hpp>
 #include <sycl/__impl/detail/config.hpp>
 #include <sycl/__impl/detail/obj_utils.hpp>
+#include <sycl/__impl/info/device_type.hpp>
 #include <sycl/__impl/info/platform.hpp>
 
 #include <memory>
@@ -25,6 +27,8 @@
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
+class device;
+
 namespace detail {
 class PlatformImpl;
 } // namespace detail
@@ -56,6 +60,16 @@ class _LIBSYCL_EXPORT platform {
   /// \return the backend associated with this platform.
   backend get_backend() const noexcept;
 
+  /// Returns all SYCL devices associated with this platform.
+  ///
+  /// If there are no devices that match given device
+  /// type, resulting vector is empty.
+  ///
+  /// \param DeviceType is a SYCL device type.
+  /// \return a vector of SYCL devices.
+  std::vector<device>
+  get_devices(info::device_type DeviceType = info::device_type::all) const;
+
   /// Queries this SYCL platform for info.
   ///
   /// The return type depends on information being queried.
@@ -69,6 +83,16 @@ class _LIBSYCL_EXPORT platform {
   typename detail::is_backend_info_desc<Param>::return_type
   get_backend_info() const;
 
+  /// Indicates if all of the SYCL devices on this platform have the
+  /// given feature.
+  ///
+  /// \param Aspect is one of the values in Table 4.20 of the SYCL 2020
+  /// Provisional Spec.
+  ///
+  /// \return true if all of the SYCL devices on this platform have the
+  /// given feature.
+  bool has(aspect Aspect) const;
+
   /// Returns all SYCL platforms from all backends that are available in the
   /// system.
   ///
diff --git a/libsycl/include/sycl/sycl.hpp b/libsycl/include/sycl/sycl.hpp
index ef91ab2381770..3e7f81092256c 100644
--- a/libsycl/include/sycl/sycl.hpp
+++ b/libsycl/include/sycl/sycl.hpp
@@ -14,6 +14,8 @@
 #ifndef _LIBSYCL_SYCL_HPP
 #define _LIBSYCL_SYCL_HPP
 
+#include <sycl/__impl/device.hpp>
+#include <sycl/__impl/device_selector.hpp>
 #include <sycl/__impl/exception.hpp>
 #include <sycl/__impl/platform.hpp>
 
diff --git a/libsycl/src/CMakeLists.txt b/libsycl/src/CMakeLists.txt
index 7ee228c8f7485..0a83f2ef36443 100644
--- a/libsycl/src/CMakeLists.txt
+++ b/libsycl/src/CMakeLists.txt
@@ -83,7 +83,10 @@ endfunction(add_sycl_rt_library)
 set(LIBSYCL_SOURCES
     "exception.cpp"
     "exception_list.cpp"
+    "device.cpp"
+    "device_selector.cpp"
     "platform.cpp"
+    "detail/device_impl.cpp"
     "detail/global_objects.cpp"
     "detail/platform_impl.cpp"
     "detail/offload/offload_utils.cpp"
diff --git a/libsycl/src/detail/device_impl.cpp b/libsycl/src/detail/device_impl.cpp
new file mode 100644
index 0000000000000..de702cc4b7839
--- /dev/null
+++ b/libsycl/src/detail/device_impl.cpp
@@ -0,0 +1,55 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include <detail/device_impl.hpp>
+#include <detail/platform_impl.hpp>
+
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+
+namespace detail {
+
+bool DeviceImpl::has(aspect Aspect) const {
+  switch (Aspect) {
+  case (aspect::cpu):
+    return isCPU();
+  case (aspect::gpu):
+    return isGPU();
+  case (aspect::accelerator):
+    return isAccelerator();
+  case (aspect::custom):
+    return false;
+  case (aspect::emulated):
+    return false;
+  case (aspect::host_debuggable):
+    return false;
+  default:
+    // Other aspects are not implemented yet
+    return false;
+  }
+}
+
+info::device_type DeviceImpl::getDeviceType() const {
+  return getInfo<info::device::device_type>();
+}
+
+bool DeviceImpl::isCPU() const {
+  return getDeviceType() == info::device_type::cpu;
+}
+
+bool DeviceImpl::isGPU() const {
+  return getDeviceType() == info::device_type::gpu;
+}
+
+bool DeviceImpl::isAccelerator() const {
+  return getDeviceType() == info::device_type::accelerator;
+}
+
+backend DeviceImpl::getBackend() const { return MPlatform.getBackend(); }
+
+} // namespace detail
+_LIBSYCL_END_NAMESPACE_SYCL
diff --git a/libsycl/src/detail/device_impl.hpp b/libsycl/src/detail/device_impl.hpp
new file mode 100644
index 0000000000000..bd4f3a99561dd
--- /dev/null
+++ b/libsycl/src/detail/device_impl.hpp
@@ -0,0 +1,122 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBSYCL_DEVICE_IMPL
+#define _LIBSYCL_DEVICE_IMPL
+
+#include <sycl/__impl/detail/config.hpp>
+#include <sycl/__impl/device.hpp>
+
+#include <detail/offload/offload_utils.hpp>
+#include <detail/platform_impl.hpp>
+
+#include <OffloadAPI.h>
+
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+namespace detail {
+
+class DeviceImpl {
+  struct PrivateTag {
+    explicit PrivateTag() = default;
+  };
+  friend class PlatformImpl;
+
+public:
+  /// Constructs a SYCL device instance using the provided
+  /// offload device instance.
+  ///
+  /// \param Device is a raw offload library handle representing device.
+  /// \param Platform is a platform this device belongs to.
+  /// All device impls must be created in corresponding platform ctor.
+  explicit DeviceImpl(ol_device_handle_t Device, PlatformImpl &Platform,
+                      PrivateTag)
+      : MOffloadDevice(Device), MPlatform(Platform) {}
+
+  ~DeviceImpl() = default;
+
+  /// Queries device type from offloading runtime
+  ///
+  /// \return device type of the device
+  info::device_type getDeviceType() const;
+
+  /// Check if device is a CPU device
+  ///
+  /// \return true if SYCL device is a CPU device
+  bool isCPU() const;
+
+  /// Check if device is a GPU device
+  ///
+  /// \return true if SYCL device is a GPU device
+  bool isGPU() const;
+
+  /// Check if device is an accelerator device
+  ///
+  /// \return true if SYCL device is an accelerator device
+  bool isAccelerator() const;
+
+  /// Returns the backend associated with this device.
+  ///
+  /// \return the sycl::backend associated with this device.
+  backend getBackend() const;
+
+  /// Returns the implementation class object of platform associated with this
+  /// device.
+  ///
+  /// \return platform implementation object this device belongs to.
+  PlatformImpl &getPlatformImpl() const { return MPlatform; }
+
+  /// Checks if this device supports aspect.
+  ///
+  /// \param Aspect to perform a check of.
+  /// \return true if this device has the given aspect.
+  bool has(aspect Aspect) const;
+
+  /// Queries this device for information requested by the template parameter
+  /// param.
+  /// The return type depends on information being queried.
+  template <typename Param> typename Param::return_type getInfo() const {
+    using namespace info::device;
+    using Map = info_ol_mapping<ol_device_info_t>;
+
+    constexpr ol_device_info_t olInfo = map_info_desc<Param, ol_device_info_t>(
+        Map::M<device_type>{OL_DEVICE_INFO_TYPE},
+        Map::M<name>{OL_DEVICE_INFO_NAME},
+        Map::M<vendor>{OL_DEVICE_INFO_VENDOR},
+        Map::M<driver_version>{OL_DEVICE_INFO_DRIVER_VERSION});
+
+    size_t ExpectedSize = 0;
+    callAndThrow(olGetDeviceInfoSize, MOffloadDevice, olInfo, &ExpectedSize);
+
+    if constexpr (std::is_same_v<typename Param::return_type, std::string>) {
+      std::string Result;
+      Result.resize(ExpectedSize - 1);
+      callAndThrow(olGetDeviceInfo, MOffloadDevice, olInfo, ExpectedSize,
+                   Result.data());
+      return Result;
+    } else if constexpr (olInfo == OL_DEVICE_INFO_TYPE) {
+      assert((sizeof(DescType) == ExpectedSize) &&
+             "Size of info descriptor reported by backend doesn't match with "
+             "expected.");
+      ol_device_type_t olType{};
+      callAndThrow(olGetDeviceInfo, MOffloadDevice, olInfo, sizeof(olType),
+                   &olType);
+      return convertDeviceTypeToSYCL(olType);
+    } else
+      static_assert(false && "Info descriptor is not properly supported");
+  }
+
+private:
+  ol_device_handle_t MOffloadDevice = {};
+  PlatformImpl &MPlatform;
+};
+
+} // namespace detail
+
+_LIBSYCL_END_NAMESPACE_SYCL
+
+#endif // _LIBSYCL_DEVICE_IMPL
diff --git a/libsycl/src/detail/global_objects.cpp b/libsycl/src/detail/global_objects.cpp
index fe29f5e7d72c0..06e44a36a21e2 100644
--- a/libsycl/src/detail/global_objects.cpp
+++ b/libsycl/src/detail/global_objects.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include <detail/device_impl.hpp>
 #include <detail/global_objects.hpp>
 #include <detail/platform_impl.hpp>
 
@@ -29,7 +30,7 @@ std::vector<PlatformImplUPtr> &getPlatformCache() {
   return PlatformCache;
 }
 
-void shutdown() {
+static void shutdown() {
   // No error reporting in shutdown
   std::ignore = olShutDown();
 }
diff --git a/libsycl/src/detail/offload/offload_topology.cpp b/libsycl/src/detail/offload/offload_topology.cpp
index 624f987a5d1f4..d7cfa30678ca3 100644
--- a/libsycl/src/detail/offload/offload_topology.cpp
+++ b/libsycl/src/detail/offload/offload_topology.cpp
@@ -11,18 +11,57 @@
 #include <detail/offload/offload_utils.hpp>
 
 #include <array>
-#include <unordered_map>
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
 namespace detail {
 
+// Platforms for this backend
+range_view<const ol_platform_handle_t> OffloadTopology::getPlatforms() const {
+  return {MPlatforms.data(), MPlatforms.size()};
+}
+
+// Devices for a specific platform (platform_id is index into Platforms)
+range_view<ol_device_handle_t>
+OffloadTopology::getDevices(size_t PlatformId) const {
+  if (PlatformId >= MDeviceRange.size()) {
+    assert(false && "Platform index exceeds number of platforms.");
+    return {nullptr, 0};
+  }
+  return MDeviceRange[PlatformId];
+}
+
+void OffloadTopology::registerNewPlatformsAndDevices(
+    Platform2DevContainer &PlatformsAndDev) {
+  if (!PlatformsAndDev.size())
+    return;
+
+  // MDevices reallocation is prevented to keep correct ranges in MDeviceRange
+  MDevices.reserve(PlatformsAndDev.size());
+
+  for (auto &[Platform, NewDev] : PlatformsAndDev) {
+    MDevices.push_back(NewDev);
+
+    // Platform is not unique within PlatformsAndDev but the container is sorted
+    if (MPlatforms.empty() || MPlatforms.back() != Platform) {
+      MPlatforms.push_back(Platform);
+      range_view<ol_device_handle_t> R{&MDevices.back(), 1 /*Size == 1*/};
+      MDeviceRange.push_back(R);
+    } else {
+      // Device is inserted already, just increment device count for the current
+      // platform
+      MDeviceRange.back().len++;
+    }
+  }
+}
+
 void discoverOffloadDevices() {
   callAndThrow(olInit);
 
+  // liboffload returns devices sorted by backend + platform. We rely on this
+  // behavior during device enumeration.
   using PerBackendDataType =
-      std::array<std::pair<PlatformWithDevStorageType, size_t /*DevCount*/>,
-                 OL_PLATFORM_BACKEND_LAST>;
+      std::array<Platform2DevContainer, OL_PLATFORM_BACKEND_LAST>;
 
   PerBackendDataType Mapping;
   // olIterateDevices() calls the lambda for each device. Devices that fail
@@ -31,17 +70,19 @@ void discoverOffloadDevices() {
   // first failure and interrupt iteration.
   callNoCheck(
       olIterateDevices,
-      [](ol_device_handle_t Dev, void *User) -> bool {
-        auto *Data = static_cast<PerBackendDataType *>(User);
-        ol_platform_handle_t Plat = nullptr;
-        ol_result_t Res = callNoCheck(
-            olGetDeviceInfo, Dev, OL_DEVICE_INFO_PLATFORM, sizeof(Plat), &Plat);
+      [](ol_device_handle_t Dev, void *UserData) -> bool {
+        auto *Data = static_cast<PerBackendDataType *>(UserData);
+
+        ol_platform_handle_t Platform = nullptr;
+        ol_result_t Res =
+            callNoCheck(olGetDeviceInfo, Dev, OL_DEVICE_INFO_PLATFORM,
+                        sizeof(Platform), &Platform);
         // If an error occurs, ignore the device and continue iteration.
         if (Res != OL_SUCCESS)
           return true;
 
         ol_platform_backend_t OlBackend = OL_PLATFORM_BACKEND_UNKNOWN;
-        Res = callNoCheck(olGetPlatformInfo, Plat, OL_PLATFORM_INFO_BACKEND,
+        Res = callNoCheck(olGetPlatformInfo, Platform, OL_PLATFORM_INFO_BACKEND,
                           sizeof(OlBackend), &OlBackend);
         // If an error occurs, ignore the device and continue iteration.
         if (Res != OL_SUCCESS)
@@ -58,9 +99,7 @@ void discoverOffloadDevices() {
         if (OlBackend >= OL_PLATFORM_BACKEND_LAST)
           return true;
 
-        auto &[Map, DevCount] = (*Data)[static_cast<size_t>(OlBackend)];
-        Map[Plat].push_back(Dev);
-        DevCount++;
+        (*Data)[static_cast<size_t>(OlBackend)].push_back({Platform, Dev});
         return true;
       },
       &Mapping);
@@ -69,7 +108,7 @@ void discoverOffloadDevices() {
   for (size_t I = 0; I < OL_PLATFORM_BACKEND_LAST; ++I) {
     OffloadTopology &Topo = OffloadTopologies[I];
     Topo.setBackend(static_cast<ol_platform_backend_t>(I));
-    Topo.registerNewPlatformsAndDevices(Mapping[I].first, Mapping[I].second);
+    Topo.registerNewPlatformsAndDevices(Mapping[I]);
   }
 }
 
diff --git a/libsycl/src/detail/offload/offload_topology.hpp b/libsycl/src/detail/offload/offload_topology.hpp
index dbd98f953b7e4..4d811f1e444d5 100644
--- a/libsycl/src/detail/offload/offload_topology.hpp
+++ b/libsycl/src/detail/offload/offload_topology.hpp
@@ -14,7 +14,6 @@
 #include <OffloadAPI.h>
 
 #include <cassert>
-#include <unordered_map>
 #include <vector>
 
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
@@ -31,8 +30,8 @@ template <class T> struct range_view {
   size_t size() const { return len; }
 };
 
-using PlatformWithDevStorageType =
-    std::unordered_map<ol_platform_handle_t, std::vector<ol_device_handle_t>>;
+using Platform2DevContainer =
+    std::vector<std::pair<ol_platform_handle_t, ol_device_handle_t>>;
 
 /// Contiguous global storage of platform handlers and device handles (grouped
 /// by platform) for a backend.
@@ -45,71 +44,42 @@ struct OffloadTopology {
   /// \param B new backend value.
   void setBackend(ol_platform_backend_t B) { MBackend = B; }
 
+  /// Queries backend of this topology.
+  ///
+  /// \returns backend of this topology.
+  ol_platform_backend_t getBackend() const { return MBackend; }
+
   /// Returns all platforms associated with this topology.
   ///
   /// \returns minimal span-like view to platforms associated with this
   /// topology.
-  range_view<const ol_platform_handle_t> platforms() const {
-    return {MPlatforms.data(), MPlatforms.size()};
-  }
+  range_view<const ol_platform_handle_t> getPlatforms() const;
 
   /// Returns all devices associated with specific platform.
   ///
-  /// \param PlatformId platform_id is index into MPlatforms.
+  /// \param PlatformId is index into MPlatforms.
   ///
   /// \returns minimal span-like view to devices associated with specified
   /// platform.
-  range_view<const ol_device_handle_t>
-  devicesForPlatform(size_t PlatformId) const {
-    if (PlatformId >= MDevRangePerPlatformId.size()) {
-      assert(false && "Platform index exceeds number of platforms.");
-      return {nullptr, 0};
-    }
-    return MDevRangePerPlatformId[PlatformId];
-  }
+  range_view<ol_device_handle_t> getDevices(size_t PlatformId) const;
 
   /// Register new platform and devices into this topology.
   ///
-  /// \param PlatformsAndDev associative container with platforms & devices.
-  /// \param TotalDevCount total device count for the platform.
-  void
-  registerNewPlatformsAndDevices(PlatformWithDevStorageType &PlatformsAndDev,
-                                 size_t TotalDevCount) {
-    if (!PlatformsAndDev.size())
-      return;
-
-    MPlatforms.reserve(PlatformsAndDev.size());
-    MDevRangePerPlatformId.reserve(MPlatforms.size());
-    MDevices.reserve(TotalDevCount);
-
-    for (auto &[NewPlatform, NewDevs] : PlatformsAndDev) {
-      MPlatforms.push_back(NewPlatform);
-      range_view<const ol_device_handle_t> R{MDevices.data() + MDevices.size(),
-                                             NewDevs.size()};
-      MDevices.insert(MDevices.end(), NewDevs.begin(), NewDevs.end());
-      MDevRangePerPlatformId.push_back(R);
-    }
-
-    assert(TotalDevCount == MDevices.size());
-  }
-
-  /// Queries backend of this topology.
-  ///
-  /// \returns backend of this topology.
-  ol_platform_backend_t backend() const { return MBackend; }
+  /// \param PlatformsAndDev collection of platforms & devices.
+  void registerNewPlatformsAndDevices(Platform2DevContainer &PlatformsAndDev);
 
 private:
   ol_platform_backend_t MBackend = OL_PLATFORM_BACKEND_UNKNOWN;
 
   // Platforms and devices belonging to this backend (flattened)
   std::vector<ol_platform_handle_t> MPlatforms;
-  std::vector<ol_device_handle_t> MDevices; // sorted by platform
+
+  // Devices are sorted by platform (guarantee from liboffload)
+  std::vector<ol_device_handle_t> MDevices;
 
   // Vector holding range of devices for each platform (index is platform index
-  // within MPlatforms)
-  std::vector<range_view<const ol_device_handle_t>>
-      MDevRangePerPlatformId; // MDevRangePerPlatformId.size() ==
-                              // MPlatforms.size()
+  // within Platforms), so MDeviceRange.size() == MPlatforms.size()
+  std::vector<range_view<ol_device_handle_t>> MDeviceRange;
 };
 
 // Initialize the topologies by calling olIterateDevices.
diff --git a/libsycl/src/detail/offload/offload_utils.cpp b/libsycl/src/detail/offload/offload_utils.cpp
index ed3d197672218..9a2609daddcee 100644
--- a/libsycl/src/detail/offload/offload_utils.cpp
+++ b/libsycl/src/detail/offload/offload_utils.cpp
@@ -56,8 +56,35 @@ backend convertBackend(ol_platform_backend_t Backend) {
   case OL_PLATFORM_BACKEND_AMDGPU:
     return backend::hip;
   default:
-    throw exception(make_error_code(errc::runtime),
-                    "convertBackend: Unsupported backend");
+    throw exception(make_error_code(errc::runtime), "Unsupported backend");
+  }
+}
+
+ol_device_type_t convertDeviceTypeToOL(info::device_type DeviceType) {
+  switch (DeviceType) {
+  case info::device_type::all:
+    return OL_DEVICE_TYPE_ALL;
+  case info::device_type::gpu:
+    return OL_DEVICE_TYPE_GPU;
+  case info::device_type::cpu:
+    return OL_DEVICE_TYPE_CPU;
+  case info::device_type::automatic:
+    return OL_DEVICE_TYPE_DEFAULT;
+  default:
+    throw exception(sycl::make_error_code(sycl::errc::runtime),
+                    "Device type is not supported");
+  }
+}
+
+info::device_type convertDeviceTypeToSYCL(ol_device_type_t DeviceType) {
+  switch (DeviceType) {
+  case OL_DEVICE_TYPE_GPU:
+    return info::device_type::gpu;
+  case OL_DEVICE_TYPE_CPU:
+    return info::device_type::cpu;
+  default:
+    throw exception(sycl::make_error_code(sycl::errc::runtime),
+                    "Device type is not supported");
   }
 }
 
diff --git a/libsycl/src/detail/offload/offload_utils.hpp b/libsycl/src/detail/offload/offload_utils.hpp
index e6113e5479f97..f32326fb87fc9 100644
--- a/libsycl/src/detail/offload/offload_utils.hpp
+++ b/libsycl/src/detail/offload/offload_utils.hpp
@@ -12,6 +12,7 @@
 #include <sycl/__impl/backend.hpp>
 #include <sycl/__impl/detail/config.hpp>
 #include <sycl/__impl/exception.hpp>
+#include <sycl/__impl/info/device_type.hpp>
 
 #include <OffloadAPI.h>
 
@@ -85,6 +86,20 @@ void callAndThrow(FunctionType &Function, ArgsT &&...Args) {
 /// \returns sycl::backend matching specified liboffload backend.
 backend convertBackend(ol_platform_backend_t Backend);
 
+/// Converts SYCL device type to liboffload type.
+///
+/// \param DeviceType SYCL device type.
+///
+/// \returns ol_device_type_t matching specified SYCL device type.
+ol_device_type_t convertDeviceTypeToOL(info::device_type DeviceType);
+
+/// Converts liboffload device type to SYCL type.
+///
+/// \param DeviceType liboffload device type.
+///
+/// \returns SYCL device type matching specified liboffload device type.
+info::device_type convertDeviceTypeToSYCL(ol_device_type_t DeviceType);
+
 /// Helper to map SYCL information descriptors to OL_<HANDLE>_INFO_<SMTH>.
 ///
 /// Typical usage:
diff --git a/libsycl/src/detail/platform_impl.cpp b/libsycl/src/detail/platform_impl.cpp
index 28bf709d5c074..5df86f7742c5d 100644
--- a/libsycl/src/detail/platform_impl.cpp
+++ b/libsycl/src/detail/platform_impl.cpp
@@ -9,9 +9,13 @@
 #include <sycl/__impl/detail/config.hpp>
 #include <sycl/__impl/detail/obj_utils.hpp>
 
+#include <detail/device_impl.hpp>
 #include <detail/global_objects.hpp>
 #include <detail/platform_impl.hpp>
 
+#include <algorithm>
+#include <memory>
+
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
 namespace detail {
@@ -19,6 +23,7 @@ namespace detail {
 PlatformImpl &PlatformImpl::getPlatformImpl(ol_platform_handle_t Platform) {
   auto &PlatformCache = getPlatformCache();
   for (auto &PlatImpl : PlatformCache) {
+    assert(PlatImpl && "Platform impl can not be nullptr");
     if (PlatImpl->getHandleRef() == Platform)
       return *PlatImpl;
   }
@@ -32,10 +37,11 @@ PlatformImpl &PlatformImpl::getPlatformImpl(ol_platform_handle_t Platform) {
 const std::vector<PlatformImplUPtr> &PlatformImpl::getPlatforms() {
   [[maybe_unused]] static auto InitPlatformsOnce = []() {
     discoverOffloadDevices();
+
     auto &PlatformCache = getPlatformCache();
     for (const auto &Topo : getOffloadTopologies()) {
       size_t PlatformIndex = 0;
-      for (const auto &OffloadPlatform : Topo.platforms()) {
+      for (const auto &OffloadPlatform : Topo.getPlatforms()) {
         PlatformCache.emplace_back(std::make_unique<PlatformImpl>(
             OffloadPlatform, PlatformIndex++, PrivateTag{}));
       }
@@ -53,6 +59,62 @@ PlatformImpl::PlatformImpl(ol_platform_handle_t Platform, size_t PlatformIndex,
                sizeof(Backend), &Backend);
   MBackend = convertBackend(Backend);
   MOffloadBackend = Backend;
+
+  const auto &Topologies = getOffloadTopologies();
+  auto RootTopologyIt = std::find_if(
+      Topologies.begin(), Topologies.end(), [&](const OffloadTopology &Topo) {
+        return Topo.getBackend() == MOffloadBackend;
+      });
+
+  assert(RootTopologyIt != Topologies.end() &&
+         "Root topology for platform must always exist");
+  auto DevRange = RootTopologyIt->getDevices(MOffloadPlatformIndex);
+  MRootDevices.reserve(DevRange.size());
+  std::for_each(DevRange.begin(), DevRange.end(),
+                [&](const ol_device_handle_t &Device) {
+                  MRootDevices.emplace_back(std::make_unique<DeviceImpl>(
+                      Device, *this, DeviceImpl::PrivateTag{}));
+                });
+}
+
+const std::vector<DeviceImplUPtr> &PlatformImpl::getRootDevices() const {
+  return MRootDevices;
+}
+
+bool PlatformImpl::has(aspect Aspect) const {
+  const auto &Devices = getRootDevices();
+  return std::all_of(
+      Devices.begin(), Devices.end(),
+      [&Aspect](const DeviceImplUPtr &Device) { return Device->has(Aspect); });
 }
+
+void PlatformImpl::iterateDevices(
+    info::device_type DeviceType,
+    std::function<void(DeviceImpl *)> callback) const {
+  // Early exit if host device is requested
+  if (DeviceType == info::device_type::host)
+    return;
+  if (DeviceType == info::device_type::custom)
+    return;
+  if (DeviceType == info::device_type::accelerator)
+    return;
+
+  const auto &DeviceImpls = getRootDevices();
+
+  // TODO: need an way to get default device from liboffload
+  // as temporal solution just return the first device for DeviceType ==
+  // automatic
+  bool KeepAll = DeviceType == info::device_type::all;
+  for (auto &Impl : DeviceImpls) {
+    if (DeviceType == info::device_type::automatic) {
+      callback(Impl.get());
+      return;
+    }
+
+    if (KeepAll || DeviceType == Impl->getDeviceType())
+      callback(Impl.get());
+  }
+}
+
 } // namespace detail
 _LIBSYCL_END_NAMESPACE_SYCL
diff --git a/libsycl/src/detail/platform_impl.hpp b/libsycl/src/detail/platform_impl.hpp
index a17b5d70a1828..45e473104985a 100644
--- a/libsycl/src/detail/platform_impl.hpp
+++ b/libsycl/src/detail/platform_impl.hpp
@@ -13,10 +13,12 @@
 #include <sycl/__impl/detail/config.hpp>
 #include <sycl/__impl/platform.hpp>
 
+#include <detail/device_impl.hpp>
 #include <detail/offload/offload_utils.hpp>
 
 #include <OffloadAPI.h>
 
+#include <functional>
 #include <memory>
 #include <string>
 #include <type_traits>
@@ -26,7 +28,10 @@ _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
 namespace detail {
 
+class DeviceImpl;
+
 using PlatformImplUPtr = std::unique_ptr<PlatformImpl>;
+using DeviceImplUPtr = std::unique_ptr<DeviceImpl>;
 
 class PlatformImpl {
   struct PrivateTag {
@@ -73,7 +78,17 @@ class PlatformImpl {
   /// \return the PlatformImpl representing the offloading RT platform.
   static PlatformImpl &getPlatformImpl(ol_platform_handle_t Platform);
 
-  /// Queries this platform for info.
+  /// Indicates if all of the SYCL devices on this platform have the
+  /// given feature.
+  ///
+  /// \param Aspect is one of the values in Table 4.20 of the SYCL 2020
+  /// Provisional Spec.
+  ///
+  /// \return true all of the SYCL devices on this platform have the
+  /// given feature.
+  bool has(aspect Aspect) const;
+
+  /// Queries this SYCL platform for info.
   ///
   /// The return type depends on information being queried.
   template <typename Param> typename Param::return_type getInfo() const {
@@ -99,11 +114,20 @@ class PlatformImpl {
     return Result;
   }
 
+  /// Calls "callback" with every root device of type == DeviceType associated
+  /// with this platform
+  void iterateDevices(info::device_type DeviceType,
+                      std::function<void(DeviceImpl *)> callback) const;
+
 private:
+  const std::vector<DeviceImplUPtr> &getRootDevices() const;
+
   ol_platform_handle_t MOffloadPlatform{};
   size_t MOffloadPlatformIndex{};
   ol_platform_backend_t MOffloadBackend{OL_PLATFORM_BACKEND_UNKNOWN};
   backend MBackend{};
+
+  std::vector<DeviceImplUPtr> MRootDevices;
 };
 
 } // namespace detail
diff --git a/libsycl/src/device.cpp b/libsycl/src/device.cpp
new file mode 100644
index 0000000000000..0d09907ba6ba8
--- /dev/null
+++ b/libsycl/src/device.cpp
@@ -0,0 +1,104 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include <sycl/__impl/device.hpp>
+
+#include <detail/device_impl.hpp>
+#include <detail/platform_impl.hpp>
+
+#include <algorithm>
+
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+
+device::device() : device(default_selector_v) {}
+
+bool device::is_cpu() const { return impl->isCPU(); }
+
+bool device::is_gpu() const { return impl->isGPU(); }
+
+bool device::is_accelerator() const { return impl->isAccelerator(); }
+
+platform device::get_platform() const {
+  return detail::createSyclObjFromImpl<platform>(impl->getPlatformImpl());
+}
+
+backend device::get_backend() const noexcept { return impl->getBackend(); }
+
+std::vector<device> device::get_devices(info::device_type DeviceType) {
+  std::vector<device> Devices;
+
+  // Not calling platform::get_devices to avoid multiple vector packing
+  for (auto &PlatformImpl : detail::PlatformImpl::getPlatforms()) {
+    assert(platformImpl && "platformImpl can not be nullptr");
+    PlatformImpl->iterateDevices(
+        DeviceType, [&Devices](detail::DeviceImpl *DevImpl) {
+          assert(DevImpl && "Device impl can't be nullptr");
+          Devices.push_back(detail::createSyclObjFromImpl<device>(*DevImpl));
+        });
+  }
+
+  return Devices;
+}
+
+template <info::partition_property prop>
+std::vector<device> device::create_sub_devices(size_t ComputeUnits) const {
+  throw exception(make_error_code(errc::feature_not_supported),
+                  "Partitioning is not supported.");
+}
+
+template _LIBSYCL_EXPORT std::vector<device>
+device::create_sub_devices<info::partition_property::partition_equally>(
+    size_t ComputeUnits) const;
+
+template <info::partition_property prop>
+std::vector<device>
+device::create_sub_devices(const std::vector<size_t> &Counts) const {
+  throw exception(make_error_code(errc::feature_not_supported),
+                  "Partitioning is not supported.");
+}
+
+template _LIBSYCL_EXPORT std::vector<device>
+device::create_sub_devices<info::partition_property::partition_by_counts>(
+    const std::vector<size_t> &Counts) const;
+
+template <info::partition_property prop>
+std::vector<device> device::create_sub_devices(
+    info::partition_affinity_domain AffinityDomain) const {
+  throw exception(make_error_code(errc::feature_not_supported),
+                  "Partitioning is not supported.");
+}
+
+template _LIBSYCL_EXPORT std::vector<device> device::create_sub_devices<
+    info::partition_property::partition_by_affinity_domain>(
+    info::partition_affinity_domain AffinityDomain) const;
+
+bool device::has(aspect Aspect) const { return impl->has(Aspect); }
+
+template <typename Param>
+detail::is_device_info_desc_t<Param> device::get_info() const {
+  return impl->getInfo<Param>();
+}
+
+template <>
+_LIBSYCL_EXPORT detail::is_device_info_desc_t<info::device::platform>
+device::get_info<info::device::platform>() const {
+  static_assert(
+      std::is_same_v<info::device::platform::return_type, sycl::platform>);
+  return get_platform();
+}
+
+#define _LIBSYCL_EXPORT_GET_INFO(Desc)                                         \
+  template _LIBSYCL_EXPORT detail::is_device_info_desc_t<info::device::Desc>   \
+  device::get_info<info::device::Desc>() const;
+_LIBSYCL_EXPORT_GET_INFO(device_type)
+_LIBSYCL_EXPORT_GET_INFO(name)
+_LIBSYCL_EXPORT_GET_INFO(vendor)
+_LIBSYCL_EXPORT_GET_INFO(driver_version)
+#undef _LIBSYCL_EXPORT_GET_INFO
+
+_LIBSYCL_END_NAMESPACE_SYCL
diff --git a/libsycl/src/device_selector.cpp b/libsycl/src/device_selector.cpp
new file mode 100644
index 0000000000000..a5da59fad7dae
--- /dev/null
+++ b/libsycl/src/device_selector.cpp
@@ -0,0 +1,123 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include <sycl/__impl/device.hpp>
+#include <sycl/__impl/device_selector.hpp>
+
+#include <detail/device_impl.hpp>
+
+#include <algorithm>
+
+_LIBSYCL_BEGIN_NAMESPACE_SYCL
+
+static constexpr int MatchedTypeDefaultScore = 1000;
+static constexpr int GPUDeviceDefaultScore = 500;
+static constexpr int CPUDeviceDefaultScore = 300;
+static constexpr int AccDeviceDefaultScore = 75;
+static constexpr int RejectDeviceScore = -1;
+
+static int getDevicePreference(const device &Device) {
+  int Score = 0;
+  const auto &DeviceImpl = detail::getSyclObjImpl(Device);
+
+  // TODO: increase score for devices with compatible dev images
+
+  if (DeviceImpl->getBackend() == backend::level_zero)
+    Score += 50;
+
+  return Score;
+}
+
+_LIBSYCL_EXPORT int default_selector_v(const device &dev) {
+  int Score = 0;
+
+  if (dev.is_gpu())
+    Score += GPUDeviceDefaultScore;
+
+  if (dev.is_cpu())
+    Score += CPUDeviceDefaultScore;
+
+  if (dev.is_accelerator())
+    Score += AccDeviceDefaultScore;
+
+  Score += getDevicePreference(dev);
+
+  return Score;
+}
+
+_LIBSYCL_EXPORT int gpu_selector_v(const device &Dev) {
+  return Dev.is_gpu() ? MatchedTypeDefaultScore + getDevicePreference(Dev)
+                      : RejectDeviceScore;
+}
+
+_LIBSYCL_EXPORT int cpu_selector_v(const device &Dev) {
+  return Dev.is_cpu() ? MatchedTypeDefaultScore + getDevicePreference(Dev)
+                      : RejectDeviceScore;
+}
+
+_LIBSYCL_EXPORT int accelerator_selector_v(const device &Dev) {
+  return Dev.is_accelerator()
+             ? MatchedTypeDefaultScore + getDevicePreference(Dev)
+             : RejectDeviceScore;
+}
+
+_LIBSYCL_EXPORT detail::DeviceSelectorInvocableType
+aspect_selector(const std::vector<aspect> &RequireList,
+                const std::vector<aspect> &DenyList) {
+  return [=](const sycl::device &Dev) {
+    // 4.6.1.1. Device selector:
+    // If no aspects are passed in, the generated selector behaves like
+    // default_selector_v.
+    if (RequireList.empty() && DenyList.empty())
+      return default_selector_v(Dev);
+
+    auto HasAspect = [&Dev](const aspect &Aspect) -> bool {
+      return Dev.has(Aspect);
+    };
+    if (!std::all_of(RequireList.begin(), RequireList.end(), HasAspect))
+      return RejectDeviceScore;
+
+    if (std::any_of(DenyList.begin(), DenyList.end(), HasAspect))
+      return RejectDeviceScore;
+
+    return MatchedTypeDefaultScore + getDevicePreference(Dev);
+  };
+}
+
+namespace detail {
+
+_LIBSYCL_EXPORT device
+SelectDevice(const DeviceSelectorInvocableType &DeviceSelector) {
+  int ChosenDeviceScore = RejectDeviceScore;
+  const device *ChosenDevice = nullptr;
+
+  std::vector<device> Devices = device::get_devices();
+  for (const auto &Device : Devices) {
+    int CurrentDevScore = DeviceSelector(Device);
+    if (CurrentDevScore < 0)
+      continue;
+
+    if ((ChosenDeviceScore < CurrentDevScore) ||
+        ((ChosenDeviceScore == CurrentDevScore) &&
+         (getDevicePreference(*ChosenDevice) < getDevicePreference(Device)))) {
+      ChosenDevice = &Device;
+      ChosenDeviceScore = CurrentDevScore;
+    }
+  }
+
+  if (ChosenDevice != nullptr) {
+    return *ChosenDevice;
+  }
+
+  throw exception(make_error_code(errc::runtime),
+                  "No device of requested type is available");
+}
+
+} // namespace detail
+
+_LIBSYCL_END_NAMESPACE_SYCL
diff --git a/libsycl/src/platform.cpp b/libsycl/src/platform.cpp
index 7f401583d6693..c04c0a6281774 100644
--- a/libsycl/src/platform.cpp
+++ b/libsycl/src/platform.cpp
@@ -8,10 +8,9 @@
 
 #include <sycl/__impl/platform.hpp>
 
+#include <detail/device_impl.hpp>
 #include <detail/platform_impl.hpp>
 
-#include <stdexcept>
-
 _LIBSYCL_BEGIN_NAMESPACE_SYCL
 
 backend platform::get_backend() const noexcept { return impl->getBackend(); }
@@ -21,12 +20,24 @@ std::vector<platform> platform::get_platforms() {
   std::vector<platform> Platforms;
   Platforms.reserve(PlatformImpls.size());
   for (auto &PlatformImpl : PlatformImpls) {
-    platform Platform = detail::createSyclObjFromImpl<platform>(*PlatformImpl);
-    Platforms.push_back(std::move(Platform));
+    Platforms.emplace_back(
+        detail::createSyclObjFromImpl<platform>(*PlatformImpl.get()));
   }
   return Platforms;
 }
 
+std::vector<device> platform::get_devices(info::device_type DeviceType) const {
+  std::vector<device> Devices;
+  impl->iterateDevices(DeviceType, [&Devices](detail::DeviceImpl *DevImpl) {
+    assert(DevImpl && "Device impl can't be nullptr");
+    Devices.push_back(detail::createSyclObjFromImpl<device>(*DevImpl));
+  });
+
+  return Devices;
+}
+
+bool platform::has(aspect Aspect) const { return impl->has(Aspect); }
+
 template <typename Param>
 detail::is_platform_info_desc_t<Param> platform::get_info() const {
   return impl->getInfo<Param>();
diff --git a/libsycl/tools/sycl-ls/sycl-ls.cpp b/libsycl/tools/sycl-ls/sycl-ls.cpp
index bd6c10899d748..3d23ba40ee42d 100644
--- a/libsycl/tools/sycl-ls/sycl-ls.cpp
+++ b/libsycl/tools/sycl-ls/sycl-ls.cpp
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 //
-// The "sycl-ls" utility lists all platforms discovered by SYCL.
+// The "sycl-ls" utility lists all platforms & devices discovered by SYCL.
 //
 // There are two types of output:
 //   concise (default) and
@@ -36,14 +36,68 @@ inline std::string_view getBackendName(const backend &Backend) {
   return "";
 }
 
+std::string getDeviceTypeName(const device &Device) {
+  auto DeviceType = Device.get_info<info::device::device_type>();
+  switch (DeviceType) {
+  case info::device_type::cpu:
+    return "cpu";
+  case info::device_type::gpu:
+    return "gpu";
+  case info::device_type::host:
+    return "host";
+  case info::device_type::accelerator:
+    return "fpga";
+  default:
+    return "unknown";
+  }
+}
+
+static void printDeviceInfo(const device &Device, bool Verbose,
+                            const std::string &Prepend) {
+  auto DeviceName = Device.get_info<info::device::name>();
+  auto DeviceVendor = Device.get_info<info::device::vendor>();
+  auto DeviceDriverVersion = Device.get_info<info::device::driver_version>();
+
+  if (Verbose) {
+    std::cout << Prepend << "Type              : " << getDeviceTypeName(Device)
+              << std::endl;
+    std::cout << Prepend << "Name              : " << DeviceName << std::endl;
+    std::cout << Prepend << "Vendor            : " << DeviceVendor << std::endl;
+    std::cout << Prepend << "Driver            : " << DeviceDriverVersion
+              << std::endl;
+  } else {
+    std::cout << Prepend << ", " << DeviceName << " [" << DeviceDriverVersion
+              << "]" << std::endl;
+  }
+}
+
+static void
+printSelectorChoice(const detail::DeviceSelectorInvocableType &Selector,
+                    const std::string &Prepend) {
+  try {
+    const auto &Device = device(Selector);
+    std::string DeviceTypeName = getDeviceTypeName(Device);
+    auto Platform = Device.get_info<info::device::platform>();
+    auto PlatformName = Platform.get_info<info::platform::name>();
+    printDeviceInfo(Device, false /*Verbose*/,
+                    Prepend + DeviceTypeName + ", " + PlatformName);
+  } catch (const sycl::exception &Exception) {
+    std::string What = Exception.what();
+    constexpr size_t MaxLength = 50;
+    // Truncate long string so it can fit in one-line
+    if (What.length() > MaxLength)
+      What = What.substr(0, MaxLength) + "...";
+    std::cout << Prepend << What << std::endl;
+  }
+}
+
 int main(int argc, char **argv) {
   llvm::cl::opt<bool> Verbose(
-      "verbose",
-      llvm::cl::desc("Verbosely prints all the discovered platforms"));
+      "verbose", llvm::cl::desc("Verbosely prints all the discovered devices"));
   llvm::cl::alias VerboseShort("v", llvm::cl::desc("Alias for -verbose"),
                                llvm::cl::aliasopt(Verbose));
   llvm::cl::ParseCommandLineOptions(
-      argc, argv, "This program lists all backends discovered by SYCL");
+      argc, argv, "This program lists all devices discovered by SYCL");
 
   try {
     const auto &Platforms = platform::get_platforms();
@@ -55,8 +109,17 @@ int main(int argc, char **argv) {
 
     for (const auto &Platform : Platforms) {
       backend Backend = Platform.get_backend();
-      std::cout << "[" << getBackendName(Backend) << ":"
-                << "unknown" << "]" << std::endl;
+      auto PlatformName = Platform.get_info<info::platform::name>();
+      const auto &Devices = Platform.get_devices();
+
+      for (const auto &Device : Devices) {
+        std::cout << "[" << getBackendName(Backend) << ":"
+                  << getDeviceTypeName(Device) << "]";
+        std::cout << " ";
+        // Verbose parameter is set to false to print regular devices output
+        // first
+        printDeviceInfo(Device, false, PlatformName);
+      }
     }
 
     if (Verbose) {
@@ -71,8 +134,19 @@ int main(int argc, char **argv) {
         std::cout << "    Version  : " << PlatformVersion << std::endl;
         std::cout << "    Name     : " << PlatformName << std::endl;
         std::cout << "    Vendor   : " << PlatformVendor << std::endl;
-        std::cout << "    Devices  : " << "unknown" << std::endl;
+
+        const auto &Devices = Platform.get_devices();
+        std::cout << "    Devices  : " << Devices.size() << std::endl;
+        for (const auto &Device : Devices) {
+          printDeviceInfo(Device, true, "        ");
+        }
       }
+
+      // Print built-in device selectors choice
+      printSelectorChoice(default_selector_v, "default_selector()      : ");
+      printSelectorChoice(accelerator_selector_v, "accelerator_selector()  : ");
+      printSelectorChoice(cpu_selector_v, "cpu_selector()          : ");
+      printSelectorChoice(gpu_selector_v, "gpu_selector()          : ");
     }
   } catch (sycl::exception &e) {
     std::cerr << "SYCL Exception encountered: " << e.what() << std::endl



More information about the llvm-commits mailing list