[llvm] [Offload] Introduce offload-tblgen and initial new API implementation (PR #108413)

Callum Fare via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 12 08:57:58 PDT 2024


https://github.com/callumfare created https://github.com/llvm/llvm-project/pull/108413

Introduce `offload-tblgen` and an initial implementation of a subset of the new API. The tablegen files are intended to be the single source of truth for the new API, with the header files, documentation, and others bits of source all automatically generated.

### offload-tblgen

See the included [README](https://github.com/callumfare/llvm-project/blob/d80db06491d85444bb6f7e59d8068a22cef3a6b4/offload/new-api/API/README.md) for more information on how the API definition and generation works. I'm happy to answer any questions about it and plan to walk through it in a future LLVM Offload call.

It should be noted that struct definitions have not been fully implemented/tested as they aren't used by the initial API definitions, but finishing that off in the future shouldn't be too much work.

The tablegen tooling has been designed to be easily extended with new backends, using the classes in `RecordTypes.hpp` to abstract over the tablegen records.

### New API

Previous discussions at the LLVM/Offload meeting have brought up the need for a new API for exposing the functionality of the plugins. This change introduces a very small subset of a new API, which is primarily for testing the offload tooling and demonstrating how a new API can fit into the existing code base without being too disruptive. Exact designs for these entry points and future additions can be worked out over time.

The new API does however introduce the bare minimum functionality to implement device discovery for Unified Runtime and SYCL. This means that the `urinfo` and `sycl-ls` tools can be used on top of Offload. A (rough) implementation of a Unified Runtime adapter (aka plugin) for Offload is available [here](https://github.com/callumfare/unified-runtime/tree/offload_adapter). Our intention is to maintain this and use it to implement and test Offload API changes with SYCL.

### Demoing the new API

```sh
$ git clone -b offload_adapter https://github.com/callumfare/unified-runtime.git
$ cd unified-runtime
$ mkdir build
$ cd build
$ cmake .. -GNinja -DUR_BUILD_ADAPTER_OFFLOAD=ON \
    -DUR_OFFLOAD_INSTALL_DIR=<offload build dir containing liboffload_new.so> \
    -DUR_OFFLOAD_INCLUDE_DIR=<offload build dir containing 'offload' headers directory>
$ ninja urinfo
export LD_LIBRARY_PATH=<offload build dir containing offload plugin libraries>
$ UR_ADAPTERS_FORCE_LOAD=$PWD/lib/libur_adapter_offload.so ./bin/urinfo
[cuda:gpu][cuda:0] CUDA, NVIDIA GeForce GT 1030  [12030]
# Demo with tracing
$ OFFLOAD_TRACE=1 UR_ADAPTERS_FORCE_LOAD=$PWD/lib/libur_adapter_offload.so ./bin/urinfo
---> offloadPlatformGet(.NumEntries = 0, .phPlatforms = {}, .pNumPlatforms = 0x7ffd05e4d6e0 (2))-> OFFLOAD_RESULT_SUCCESS
---> offloadPlatformGet(.NumEntries = 2, .phPlatforms = {0x564bf4040220, 0x564bf4040240}, .pNumPlatforms = nullptr)-> OFFLOAD_RESULT_SUCCESS
...
```


### Open questions and future work
* The new API is implemented in a separate library (`liboffload_new.so`). It could just as easily be part of the existing `libomptarget` library - I have no strong feelings on which is better.
* Only some of the available device info is exposed, and not all the possible device queries needed for SYCL are implemented by the plugins. A sensible next step would be to refactor and extend the existing device info queries in the plugins. The existing info queries are all strings, but the new API introduces the ability to return any arbitrary type.
* It may be sensible at some point for the plugins to implement the new API directly, and the higher level code on top of it could be made generic, but this is more of a long-term possibility.

>From d80db06491d85444bb6f7e59d8068a22cef3a6b4 Mon Sep 17 00:00:00 2001
From: Callum Fare <callum at codeplay.com>
Date: Thu, 12 Sep 2024 15:32:27 +0100
Subject: [PATCH] [Offload] Introduce offload-tblgen and initial new API
 implementation

---
 offload/CMakeLists.txt                        |   3 +
 offload/new-api/API/APIDefs.td                | 212 ++++++++++++++++
 offload/new-api/API/Common.td                 |  75 ++++++
 offload/new-api/API/Device.td                 | 101 ++++++++
 offload/new-api/API/OffloadAPI.td             |  15 ++
 offload/new-api/API/Platform.td               |  94 ++++++++
 offload/new-api/API/README.md                 | 150 ++++++++++++
 offload/new-api/CMakeLists.txt                |  36 +++
 offload/new-api/README.md                     |   8 +
 offload/new-api/src/helpers.hpp               |  96 ++++++++
 offload/new-api/src/offload_impl.cpp          | 187 +++++++++++++++
 offload/new-api/src/offload_lib.cpp           |  23 ++
 .../common/include/PluginInterface.h          |   7 +
 offload/tools/offload-tblgen/APIGen.cpp       | 196 +++++++++++++++
 offload/tools/offload-tblgen/CMakeLists.txt   |  24 ++
 .../tools/offload-tblgen/EntryPointGen.cpp    |  86 +++++++
 offload/tools/offload-tblgen/FuncsGen.cpp     |  54 +++++
 offload/tools/offload-tblgen/GenCommon.hpp    |  58 +++++
 offload/tools/offload-tblgen/Generators.hpp   |  18 ++
 offload/tools/offload-tblgen/PrintGen.cpp     | 206 ++++++++++++++++
 offload/tools/offload-tblgen/RecordTypes.hpp  | 227 ++++++++++++++++++
 .../tools/offload-tblgen/offload-tblgen.cpp   |  95 ++++++++
 22 files changed, 1971 insertions(+)
 create mode 100644 offload/new-api/API/APIDefs.td
 create mode 100644 offload/new-api/API/Common.td
 create mode 100644 offload/new-api/API/Device.td
 create mode 100644 offload/new-api/API/OffloadAPI.td
 create mode 100644 offload/new-api/API/Platform.td
 create mode 100644 offload/new-api/API/README.md
 create mode 100644 offload/new-api/CMakeLists.txt
 create mode 100644 offload/new-api/README.md
 create mode 100644 offload/new-api/src/helpers.hpp
 create mode 100644 offload/new-api/src/offload_impl.cpp
 create mode 100644 offload/new-api/src/offload_lib.cpp
 create mode 100644 offload/tools/offload-tblgen/APIGen.cpp
 create mode 100644 offload/tools/offload-tblgen/CMakeLists.txt
 create mode 100644 offload/tools/offload-tblgen/EntryPointGen.cpp
 create mode 100644 offload/tools/offload-tblgen/FuncsGen.cpp
 create mode 100644 offload/tools/offload-tblgen/GenCommon.hpp
 create mode 100644 offload/tools/offload-tblgen/Generators.hpp
 create mode 100644 offload/tools/offload-tblgen/PrintGen.cpp
 create mode 100644 offload/tools/offload-tblgen/RecordTypes.hpp
 create mode 100644 offload/tools/offload-tblgen/offload-tblgen.cpp

diff --git a/offload/CMakeLists.txt b/offload/CMakeLists.txt
index 9ffe8f56b76e67..ba7be255cfe582 100644
--- a/offload/CMakeLists.txt
+++ b/offload/CMakeLists.txt
@@ -349,6 +349,9 @@ add_subdirectory(tools)
 # Build target agnostic offloading library.
 add_subdirectory(src)
 
+add_subdirectory(tools/offload-tblgen)
+add_subdirectory(new-api)
+
 # Add tests.
 add_subdirectory(test)
 
diff --git a/offload/new-api/API/APIDefs.td b/offload/new-api/API/APIDefs.td
new file mode 100644
index 00000000000000..410a28c4c90cfe
--- /dev/null
+++ b/offload/new-api/API/APIDefs.td
@@ -0,0 +1,212 @@
+//===-- APIDefs.td - Base definitions for Offload tablegen -*- tablegen -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains the class definitions used to implement the Offload API,
+// as well as helper functions used to help populate relevant records.
+// See offload/API/README.md for more detailed documentation.
+//
+//===----------------------------------------------------------------------===//
+
+// Prefix for API naming. This could be hard-coded in the future when a value
+// is agreed upon.
+defvar PREFIX = "OFFLOAD";
+defvar prefix = !tolower(PREFIX);
+
+// Parameter flags
+defvar PARAM_IN = 0x1;
+defvar PARAM_OUT = 0x2;
+defvar PARAM_OPTIONAL = 0x4;
+defvar PARAM_IN_OPTIONAL = !or(PARAM_IN, PARAM_OPTIONAL);
+defvar PARAM_OUT_OPTIONAL = !or(PARAM_OUT, PARAM_OPTIONAL);
+
+// Does the type end with '_handle_t'?
+class IsHandleType<string Type> {
+  // size("_handle_t") == 9
+  bit ret = !if(!lt(!size(Type), 9), 0,
+                !ne(!find(Type, "_handle_t", !sub(!size(Type), 9)), -1));
+}
+
+// Does the type end with '*'?
+class IsPointerType<string Type> {
+  bit ret = !ne(!find(Type, "*", !sub(!size(Type), 1)), -1);
+}
+
+// Describes the valid range of a pointer parameter that reperesents an array
+class Range<string Begin, string End> {
+  string begin = Begin;
+  string end = End;
+}
+
+// Names the parameters that indicate the type and size of the data pointed to
+// by an opaque pointer parameter
+class TypeInfo<string TypeEnum, string TypeSize> {
+  string enum = TypeEnum;
+  string size = TypeSize;
+}
+
+class Param<string Type, string Name, string Desc, bits<3> Flags = 0> {
+  string type = Type;
+  string name = Name;
+  string desc = Desc;
+  bits<3> flags = Flags;
+  Range range = Range<"", "">;
+  TypeInfo type_info = TypeInfo<"", "">;
+  bit IsHandle = IsHandleType<type>.ret;
+  bit IsPointer = IsPointerType<type>.ret;
+}
+
+// A parameter whose range is described by other parameters in the function.
+class RangedParam<string Type, string Name, string Desc, bits<3> Flags, Range ParamRange> : Param<Type, Name, Desc, Flags> {
+  let range = ParamRange;
+}
+
+// A parameter (normally of type void*) which has its pointee type and size
+// described by other parameters in the function.
+class TypeTaggedParam<string Type, string Name, string Desc, bits<3> Flags, TypeInfo ParamTypeInfo> : Param<Type, Name, Desc, Flags> {
+  let type_info = ParamTypeInfo;
+}
+
+class Return<string Value, list<string> Conditions = []> {
+  string value = Value;
+  list<string> conditions = Conditions;
+}
+
+class ShouldCheckHandle<Param P> {
+  bit ret = !and(P.IsHandle, !eq(!and(PARAM_OPTIONAL, P.flags), 0));
+}
+
+class ShouldCheckPointer<Param P> {
+  bit ret = !and(P.IsPointer, !eq(!and(PARAM_OPTIONAL, P.flags), 0));
+}
+
+// For a list of returns that contains a specific return code, find and append
+// new conditions to that return
+class AppendConditionsToReturn<list<Return> Returns, string ReturnValue,
+                               list<string> Conditions> {
+  list<Return> ret =
+      !foreach(Ret, Returns,
+               !if(!eq(Ret.value, ReturnValue),
+                   Return<Ret.value, Ret.conditions#Conditions>, Ret));
+}
+
+// Add null handle checks to a function's return value descriptions
+class AddHandleChecksToReturns<list<Param> Params, list<Return> Returns> {
+  list<string> handle_params =
+      !foreach(P, Params, !if(ShouldCheckHandle<P>.ret, P.name, ""));
+  list<string> handle_params_filt =
+      !filter(param, handle_params, !ne(param, ""));
+  list<string> handle_param_conds =
+      !foreach(handle, handle_params_filt, "`NULL == "#handle#"`");
+
+  // Does the list of returns already contain ERROR_INVALID_NULL_HANDLE?
+  bit returns_has_inv_handle = !foldl(
+      0, Returns, HasErr, Ret,
+      !or(HasErr, !eq(Ret.value, PREFIX#"_RESULT_ERROR_INVALID_NULL_HANDLE")));
+
+  list<Return> returns_out = !if(returns_has_inv_handle,
+        AppendConditionsToReturn<Returns, PREFIX # "_RESULT_ERROR_INVALID_NULL_HANDLE", handle_param_conds>.ret,
+        !listconcat(Returns, [Return<PREFIX # "_RESULT_ERROR_INVALID_NULL_HANDLE", handle_param_conds>])
+    );
+}
+
+// Add null pointer checks to a function's return value descriptions
+class AddPointerChecksToReturns<list<Param> Params, list<Return> Returns> {
+  list<string> ptr_params =
+      !foreach(P, Params, !if(ShouldCheckPointer<P>.ret, P.name, ""));
+  list<string> ptr_params_filt = !filter(param, ptr_params, !ne(param, ""));
+  list<string> ptr_param_conds =
+      !foreach(ptr, ptr_params_filt, "`NULL == "#ptr#"`");
+
+  // Does the list of returns already contain ERROR_INVALID_NULL_POINTER?
+  bit returns_has_inv_ptr = !foldl(
+      0, Returns, HasErr, Ret,
+      !or(HasErr, !eq(Ret.value, PREFIX#"_RESULT_ERROR_INVALID_NULL_POINTER")));
+  list<Return> returns_out = !if(returns_has_inv_ptr,
+        AppendConditionsToReturn<Returns, PREFIX # "_RESULT_ERROR_INVALID_NULL_POINTER", ptr_param_conds>.ret,
+        !listconcat(Returns, [Return<PREFIX # "_RESULT_ERROR_INVALID_NULL_POINTER", ptr_param_conds>])
+    );
+}
+
+defvar DefaultReturns = [Return<PREFIX#"_RESULT_SUCCESS">,
+                         Return<PREFIX#"_RESULT_ERROR_UNINITIALIZED">,
+                         Return<PREFIX#"_RESULT_ERROR_DEVICE_LOST">];
+
+class APIObject {
+  string name;
+  string desc;
+}
+
+class Function : APIObject {
+  list<Param> params;
+  list<Return> returns;
+  list<string> details = [];
+  list<string> analogues = [];
+
+  list<Return> returns_with_def = !listconcat(DefaultReturns, returns);
+  list<Return> all_returns = AddPointerChecksToReturns<params,
+        AddHandleChecksToReturns<params, returns_with_def>.returns_out>.returns_out;
+}
+
+class Etor<string Name, string Desc> {
+  string name = Name;
+  string desc = Desc;
+  string tagged_type;
+}
+
+class TaggedEtor<string Name, string Type, string Desc> : Etor<Name, Desc> {
+  let tagged_type = Type;
+}
+
+class Enum : APIObject {
+  // This refers to whether the enumerator descriptions specify a return
+  // type for functions where this enum may be used as an output type. If set,
+  // all Etor values must be TaggedEtor records
+  bit is_typed = 0;
+
+  list<Etor> etors = [];
+}
+
+class StructMember<string Type, string Name, string Desc> {
+  string type = Type;
+  string name = Name;
+  string desc = Desc;
+}
+
+defvar DefaultPropStructMembers =
+    [StructMember<prefix#"_structure_type_t", "stype",
+                  "type of this structure">,
+     StructMember<"void*", "pNext", "pointer to extension-specific structure">];
+
+class StructHasInheritedMembers<string BaseClass> {
+  bit ret = !or(!eq(BaseClass, prefix#"_base_properties_t"),
+                !eq(BaseClass, prefix#"_base_desc_t"));
+}
+
+class Struct : APIObject {
+  string base_class = "";
+  list<StructMember> members;
+  list<StructMember> all_members =
+      !if(StructHasInheritedMembers<base_class>.ret,
+          DefaultPropStructMembers, [])#members;
+}
+
+class Typedef : APIObject { string value; }
+
+class FptrTypedef : APIObject {
+  list<Param> params;
+  list<Return> returns;
+}
+
+class Macro : APIObject {
+  string value;
+
+  string condition;
+  string alt_value;
+}
+
+class Handle : APIObject;
diff --git a/offload/new-api/API/Common.td b/offload/new-api/API/Common.td
new file mode 100644
index 00000000000000..d293e4addfef8b
--- /dev/null
+++ b/offload/new-api/API/Common.td
@@ -0,0 +1,75 @@
+def : Macro {
+  let name = "OFFLOAD_APICALL";
+  let desc = "Calling convention for all API functions";
+  let condition = "defined(_WIN32)";
+  let value = "__cdecl";
+  let alt_value = "";
+}
+
+def : Macro {
+  let name = "OFFLOAD_APIEXPORT";
+  let desc = "Microsoft-specific dllexport storage-class attribute";
+  let condition = "defined(_WIN32)";
+  let value = "__declspec(dllexport)";
+  let alt_value = "";
+}
+
+def : Macro {
+  let name = "OFFLOAD_DLLEXPORT";
+  let desc = "Microsoft-specific dllexport storage-class attribute";
+  let condition = "defined(_WIN32)";
+  let value = "__declspec(dllexport)";
+}
+
+def : Macro {
+  let name = "OFFLOAD_DLLEXPORT";
+  let desc = "GCC-specific dllexport storage-class attribute";
+  let condition = "__GNUC__ >= 4";
+  let value = "__attribute__ ((visibility (\"default\")))";
+  let alt_value = "";
+}
+
+def : Typedef {
+  let name = "offload_bool_t";
+  let value = "uint8_t";
+  let desc = "compiler-independent type";
+}
+
+def : Handle {
+  let name = "offload_platform_handle_t";
+  let desc = "Handle of a platform instance";
+}
+
+def : Handle {
+  let name = "offload_device_handle_t";
+  let desc = "Handle of platform's device object";
+}
+
+def : Handle {
+  let name = "offload_context_handle_t";
+  let desc = "Handle of context object";
+}
+
+def : Enum {
+  let name = "offload_result_t";
+  let desc = "Defines Return/Error codes";
+  let etors =[
+    Etor<"SUCCESS", "Success">,
+    Etor<"ERROR_INVALID_VALUE", "Invalid Value">,
+    Etor<"ERROR_INVALID_PLATFORM", "Invalid platform">,
+    Etor<"ERROR_DEVICE_NOT_FOUND", "Device not found">,
+    Etor<"ERROR_INVALID_DEVICE", "Invalid device">,
+    Etor<"ERROR_DEVICE_LOST", "Device hung, reset, was removed, or driver update occurred">,
+    Etor<"ERROR_UNINITIALIZED", "plugin is not initialized or specific entry-point is not implemented">,
+    Etor<"ERROR_OUT_OF_RESOURCES", "Out of resources">,
+    Etor<"ERROR_UNSUPPORTED_VERSION", "[Validation] generic error code for unsupported versions">,
+    Etor<"ERROR_UNSUPPORTED_FEATURE", "[Validation] generic error code for unsupported features">,
+    Etor<"ERROR_INVALID_ARGUMENT", "[Validation] generic error code for invalid arguments">,
+    Etor<"ERROR_INVALID_NULL_HANDLE", "[Validation] handle argument is not valid">,
+    Etor<"ERROR_INVALID_NULL_POINTER", "[Validation] pointer argument may not be nullptr">,
+    Etor<"ERROR_INVALID_SIZE", "[Validation] invalid size or dimensions (e.g., must not be zero, or is out of bounds)">,
+    Etor<"ERROR_INVALID_ENUMERATION", "[Validation] enumerator argument is not valid">,
+    Etor<"ERROR_UNSUPPORTED_ENUMERATION", "[Validation] enumerator argument is not supported by the device">,
+    Etor<"ERROR_UNKNOWN", "Unknown or internal error">
+  ];
+}
\ No newline at end of file
diff --git a/offload/new-api/API/Device.td b/offload/new-api/API/Device.td
new file mode 100644
index 00000000000000..2a330d7ef7c4cb
--- /dev/null
+++ b/offload/new-api/API/Device.td
@@ -0,0 +1,101 @@
+//===-- Device.td - Device definitions for Offload ---------*- tablegen -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains Offload API definitions related to the Device handle
+//
+//===----------------------------------------------------------------------===//
+
+def : Enum {
+  let name = "offload_device_type_t";
+  let desc = "Supported device types";
+  let etors =[
+    Etor<"DEFAULT", "The default device type as preferred by the runtime">,
+    Etor<"ALL", "Devices of all types">,
+    Etor<"GPU", "GPU device type">,
+    Etor<"CPU", "CPU device type">,
+  ];
+}
+
+def : Enum {
+  let name = "offload_device_info_t";
+  let desc = "Supported device info";
+  let is_typed = 1;
+  let etors =[
+    TaggedEtor<"TYPE", "offload_device_type_t", "type of the device">,
+    TaggedEtor<"PLATFORM", "offload_platform_handle_t", "the platform associated with the device">,
+    TaggedEtor<"NAME", "char[]", "Device name">,
+    TaggedEtor<"VENDOR", "char[]", "Device vendor">,
+    TaggedEtor<"DRIVER_VERSION", "char[]", "Driver version">
+  ];
+}
+
+def : Function {
+  let name = "offloadDeviceGet";
+  let desc = "Retrieves devices within a platform";
+  let details = [
+    "Multiple calls to this function will return identical device handles, in the same order.",
+    "The number and order of handles returned from this function can be affected by environment variables that filter devices exposed through API.",
+    "The returned devices are taken a reference of and must be released with a subsequent call to olDeviceRelease.",
+    "The application may call this function from simultaneous threads, the implementation must be thread-safe"
+  ];
+  let params = [
+    Param<"offload_platform_handle_t", "hPlatform", "handle of the platform instance", PARAM_IN>,
+    Param<"offload_device_type_t", "DeviceType", "the type of the devices.", PARAM_IN>,
+    Param<"uint32_t", "NumEntries", "the number of devices to be added to phDevices."
+        "If phDevices is not NULL, then NumEntries should be greater than zero. Otherwise OFFLOAD_RESULT_ERROR_INVALID_SIZE"
+        "will be returned.", PARAM_IN>,
+    RangedParam<"offload_device_handle_t*", "phDevices", "array of handle of devices."
+        "If NumEntries is less than the number of devices available, then platform shall only retrieve that number of devices.", PARAM_OUT_OPTIONAL,
+        Range<"0", "NumEntries">>,
+    Param<"uint32_t*", "pNumDevices", "pointer to the number of devices."
+        "pNumDevices will be updated with the total number of devices available.", PARAM_OUT_OPTIONAL>
+  ];
+  let returns = [
+    Return<"OFFLOAD_RESULT_ERROR_INVALID_SIZE", [
+      "`NumEntries == 0 && phDevices != NULL`"
+    ]>,
+    Return<"OFFLOAD_RESULT_ERROR_INVALID_NULL_POINTER", [
+      "`NumEntries > 0 && phDevices == NULL`"
+    ]>,
+    Return<"OFFLOAD_RESULT_ERROR_INVALID_VALUE">
+  ];
+}
+
+def : Function {
+  let name = "offloadDeviceGetInfo";
+  let desc = "Retrieves various information about device";
+  let details = [
+    "The application may call this function from simultaneous threads.",
+    "The implementation of this function should be lock-free."
+  ];
+  let params = [
+    Param<"offload_device_handle_t", "hDevice", "handle of the device instance", PARAM_IN>,
+    Param<"offload_device_info_t", "propName", "type of the info to retrieve", PARAM_IN>,
+    Param<"size_t", "propSize", "the number of bytes pointed to by pPropValue.", PARAM_IN>,
+    TypeTaggedParam<"void*", "pPropValue", "array of bytes holding the info. If propSize is not equal to or greater than the real "
+                    "number of bytes needed to return the info then the OFFLOAD_RESULT_ERROR_INVALID_SIZE error is returned and "
+                    "pPropValue is not used.", PARAM_OUT_OPTIONAL, TypeInfo<"propName" , "propSize">>,
+    Param<"size_t*", "pPropSizeRet", "pointer to the actual size in bytes of the queried propName.", PARAM_OUT_OPTIONAL>
+  ];
+  let returns = [
+    Return<"OFFLOAD_RESULT_ERROR_UNSUPPORTED_ENUMERATION", [
+      "If `propName` is not supported by the adapter."
+    ]>,
+    Return<"OFFLOAD_RESULT_ERROR_INVALID_SIZE", [
+      "`propSize == 0 && pPropValue != NULL`",
+      "If `propSize` is less than the real number of bytes needed to return the info."
+    ]>,
+    Return<"OFFLOAD_RESULT_ERROR_INVALID_NULL_POINTER", [
+      "`propSize != 0 && pPropValue == NULL`",
+      "`pPropValue == NULL && pPropSizeRet == NULL`"
+    ]>,
+    Return<"OFFLOAD_RESULT_ERROR_INVALID_DEVICE">,
+    Return<"OFFLOAD_RESULT_ERROR_OUT_OF_RESOURCES">,
+    Return<"OFFLOAD_RESULT_ERROR_OUT_OF_HOST_MEMORY">
+  ];
+}
diff --git a/offload/new-api/API/OffloadAPI.td b/offload/new-api/API/OffloadAPI.td
new file mode 100644
index 00000000000000..8a0c3c40581223
--- /dev/null
+++ b/offload/new-api/API/OffloadAPI.td
@@ -0,0 +1,15 @@
+//===-- OffloadAPI.td - Root tablegen file for Offload -----*- tablegen -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Always include this file first
+include "APIDefs.td"
+
+// Add API definition files here
+include "Common.td"
+include "Platform.td"
+include "Device.td"
diff --git a/offload/new-api/API/Platform.td b/offload/new-api/API/Platform.td
new file mode 100644
index 00000000000000..71af04bb831998
--- /dev/null
+++ b/offload/new-api/API/Platform.td
@@ -0,0 +1,94 @@
+//===-- Platform.td - Platform definitions for Offload -----*- tablegen -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file contains Offload API definitions related to the Platform handle
+//
+//===----------------------------------------------------------------------===//
+def : Function {
+  let name = "offloadPlatformGet";
+  let desc = "Retrieves all available platforms";
+  let details = [
+    "Multiple calls to this function will return identical platforms handles, in the same order.",
+  ];
+  let params = [
+    Param<"uint32_t", "NumEntries",
+      "The number of platforms to be added to phPlatforms. If phPlatforms is not NULL, then"
+      "NumEntries should be greater than zero, otherwise OFFLOAD_RESULT_ERROR_INVALID_SIZE"
+      "will be returned.", PARAM_IN>,
+    RangedParam<"offload_platform_handle_t*", "phPlatforms", 
+      "Array of handle of platforms. If NumEntries is"
+      "less than the number of platforms available, then offloadPlatformGet"
+      "shall only retrieve that number of platforms.",
+      PARAM_OUT_OPTIONAL, Range<"0", "NumEntries">>,
+    Param<"uint32_t*",
+      "pNumPlatforms", "returns the total number of platforms available.",
+      PARAM_OUT_OPTIONAL>
+  ];
+  let returns = [
+    Return<"OFFLOAD_RESULT_ERROR_INVALID_SIZE", [
+      "`NumEntries == 0 && phPlatforms != NULL`"
+    ]>
+  ];
+}
+
+def : Enum {
+  let name = "offload_platform_info_t";
+  let desc = "Supported platform info";
+  let is_typed = 1;
+  let etors = [
+    TaggedEtor<"NAME", "char[]", "The string denoting name of the platform. The size of the info needs to be dynamically queried.">,
+    TaggedEtor<"VENDOR_NAME", "char[]","The string denoting name of the vendor of the platform. The size of the info needs to be dynamically queried.">,
+    TaggedEtor<"VERSION", "char[]", "The string denoting the version of the platform. The size of the info needs to be dynamically queried.">,
+    TaggedEtor<"BACKEND", "offload_platform_backend_t", "The backend of the platform. Identifies the native backend adapter implementing this platform.">
+  ];
+}
+
+def : Enum {
+  let name = "offload_platform_backend_t";
+  let desc = "Identifies the native backend of the platform";
+  let etors =[
+    Etor<"UNKNOWN", "The backend is not recognized">,
+    Etor<"CUDA", "The backend is CUDA">,
+    Etor<"AMDGPU", "The backend is AMDGPU">,
+  ];
+}
+
+def : Function {
+  let name = "offloadPlatformGetInfo";
+  let desc = "Retrieves various information about platform";
+  let details = [
+    "The application may call this function from simultaneous threads.",
+    "The implementation of this function should be lock-free."
+  ];
+  let params = [
+    Param<"offload_platform_handle_t", "hPlatform", "handle of the platform", PARAM_IN>,
+    Param<"offload_platform_info_t", "propName", "type of the info to retrieve", PARAM_IN>,
+    Param<"size_t", "propSize", "the number of bytes pointed to by pPlatformInfo.", PARAM_IN>,
+    TypeTaggedParam<"void*", "pPropValue", "array of bytes holding the info."
+      "If Size is not equal to or greater to the real number of bytes needed to return the info"
+      "then the OFFLOAD_RESULT_ERROR_INVALID_SIZE error is returned and pPlatformInfo is not used.", PARAM_OUT_OPTIONAL,
+      TypeInfo<"propName" , "propSize">>,
+    Param<"size_t*", "pPropSizeRet", "pointer to the actual number of bytes being queried by pPlatformInfo.", PARAM_OUT_OPTIONAL>
+  ];
+  let returns = [
+    Return<"OFFLOAD_RESULT_ERROR_UNSUPPORTED_ENUMERATION", [
+      "If `propName` is not supported by the platform."
+    ]>,
+    Return<"OFFLOAD_RESULT_ERROR_INVALID_SIZE", [
+      "`propSize == 0 && pPropValue != NULL`",
+      "If `propSize` is less than the real number of bytes needed to return the info."
+    ]>,
+    Return<"OFFLOAD_RESULT_ERROR_INVALID_NULL_POINTER", [
+      "`propSize != 0 && pPropValue == NULL`",
+      "`pPropValue == NULL && pPropSizeRet == NULL`"
+    ]>,
+    Return<"OFFLOAD_RESULT_ERROR_INVALID_PLATFORM">,
+    Return<"OFFLOAD_RESULT_ERROR_OUT_OF_RESOURCES">,
+    Return<"OFFLOAD_RESULT_ERROR_OUT_OF_HOST_MEMORY">
+  ];
+}
diff --git a/offload/new-api/API/README.md b/offload/new-api/API/README.md
new file mode 100644
index 00000000000000..ab1db704404f35
--- /dev/null
+++ b/offload/new-api/API/README.md
@@ -0,0 +1,150 @@
+# Offload API definitions
+
+**Note**: This is a work-in-progress. It is loosely based on equivalent
+tooling in Unified Runtime.
+
+The Tablegen files in this directory are used to define the Offload API. They
+are used with the `offload-tblgen` tool to generate API headers, print headers,
+and other implementation details.
+
+The root file is `OffloadAPI.td` - additional `.td` files can be included in
+this file to add them to the API.
+
+## API Objects
+The API consists of a number of objects, which always have a *name* field and
+*description* field, and are one of the following types:
+
+### Function
+Represents an API entry point function. Has a list of returns and parameters.
+Also has fields for details (representing a bullet-point list of
+information about the function that would otherwise be too detailed for the
+description), and analogues (equivalent functions in other APIs).
+
+#### Parameter
+Represents a parameter to a function, has *type*, *name*, and *desc* fields.
+Also has a *flags* field containing flags representing whether the parameter is
+in, out, or optional.
+
+The *type* field is used to infer if the parameter is a pointer or handle type.
+A *handle* type is a pointer to an opaque struct, used to abstract over
+plugin-specific implementation details.
+
+There are two special variants of a *parameter*:
+* **RangedParameter** - Represents a parameter that has a range described by other parameters. Generally these are pointers to an arbitrary number of objects. The range is used for generating validation and printing code. E.g, a range might be between `(0, NumDevices)`
+* **TypeTaggedParameter** - Represents a parameter (usually of `void*` type) that has the type and size of its pointee data described by other function parameters. The type is usually described by a type-tagged enum. This allows functions (e.g. `offloadDeviceGetInfo`) to return data of an arbitrary type.
+
+#### Return
+A return represents a possible return code from the function, and optionally a
+list of conditions in which this value may be returned. The conditions list is
+not expected to be exhaustive. A condition is considered free-form text, but
+if it is wrapped in \`backticks\` then it is treated as literal code
+representing an error condition (e.g. `someParam < 1`). These conditions are
+used to automatically create validation checks by the `offload-tblgen`
+validation generator.
+
+Returns are automatically generated for functions with pointer or handle
+parameters, so API authors do not need to exhaustively add null checks for
+these types of parameters. All functions also get a number of default return
+values automatically.
+
+
+### Struct
+Represents a struct. Contains a list of members, which each have a *type*,
+*name*, and *desc*.
+
+Also optionally takes a *base_class* field. If this is either of the special
+`offload_base_properties_t` or `offload_base_desc_t` structs, then the struct
+will inherit members from those structs. The generated struct does **not** use
+actual C++ inheritance, but instead explicitly has those members copied in,
+which preserves ABI compatibility with C.
+
+### Enum
+Represents a C-style enum. Contains a list of `etor` values, which have a name
+and description.
+
+A `TaggedEtor` record type also exists which addtionally takes a type. This type
+is used when the enum is used as a parameter to a function with a type-tagged
+function parameter (e.g. `offloadDeviceGetInfo`).
+
+All enums automatically get a `<enum_name>_FORCE_UINT32 = 0x7fffffff` value,
+which forces the underlying type to be uint32.
+
+### Handle
+Represents a pointer to an opaque struct, as described in the Parameter section.
+It does not take any extra fields.
+
+### Typedef
+Represents a typedef, contains only a *value* field.
+
+### Macro
+Represents a C preprocessor `#define`. Contains a *value* field. Optionally
+takes a *condition* field, which allows the macro to be conditionally defined,
+and an *alt_value* field, which represents the value if the condition is false.
+
+Macro arguments are presented in the *name* field (e.g. name = `mymacro(arg)`).
+
+While there may seem little point generating a macro from tablegen, doing this
+allows the entire source of the header file to be generated from the tablegen
+files, rather than requiring a mix of C source and tablegen.
+
+## Generation
+
+### API header
+```
+./offload-tblgen -I <path-to-llvm>/offload/API  <path-to-llvm>/offload/API/OffloadAPI.td --gen-api
+```
+The comments in the generated header are in Doxygen format, although
+generating documentation from them hasn't been implemented yet.
+
+The entirety of this header is generated by Tablegen, rather than having a predefined header file that includes one or more `.inc` files. This is because this header is expected to be part of the installation and distributed to end-users, so should be self-contained.
+
+### Entry Points
+```
+./offload-tblgen -I <path-to-llvm>/offload/API  <path-to-llvm>/offload/API/OffloadAPI.td --gen-entry-points
+```
+These functions form the actual Offload interface, and are wrappers over the
+functions that contain the actual implementation (see
+'Adding a new entry point').
+
+They implement automatically generated validation checks, and tracing of
+function calls with arguments and results. The tracing can be enabled with the
+`OFFLOAD_TRACE` environment variable.
+
+### Implementation function declarations
+```
+./offload-tblgen -I <path-to-llvm>/offload/API  <path-to-llvm>/offload/API/OffloadAPI.td --gen-impl-func-decls
+```
+Generates declarations of the implementation of functions of every entry point
+in the API, e.g. `offloadDeviceFoo_impl` for `offloadDeviceFoo`.
+
+### Print header
+```
+./offload-tblgen -I <path-to-llvm>/offload/API  <path-to-llvm>/offload/API/OffloadAPI.td --gen-print-header
+```
+This header contains `std::ostream &operator<<(std::ostream&)` definitions for
+various API objects, including function parameters.
+
+As with the API header, it is expected that this header is part of the installed
+package, so it is entirely generated by Tablegen.
+
+For ease of implementation, and since it is not strictly part of the API, this
+is a C++ header file. If a C version is desirable it could be added.
+
+### Future Tablegen backends
+`RecordTypes.hpp` contains wrappers for all of the API object types, which will
+allow more backends to be easily added in future.
+
+## Adding to the API
+
+A new object can be added to the API by adding to one of the existing `.td`
+files. It is also possible to add a new tablegen file to the API by adding it
+to the includes in `OffloadAPI.td`. When the offload target is rebuilt, the
+new definition will be included in the generated files.
+
+### Adding a new entry point
+
+When a new entry point is added (e.g. `offloadDeviceFoo`), the actual entry
+point is automatically generated, which contains validation and tracing code.
+It expects an implementation function (`offloadDeviceFoo_impl`) to be defined,
+which it will call into. The definition of this implementation function should
+be added to `src/offload_impl.cpp`
\ No newline at end of file
diff --git a/offload/new-api/CMakeLists.txt b/offload/new-api/CMakeLists.txt
new file mode 100644
index 00000000000000..c27591d2af3ab3
--- /dev/null
+++ b/offload/new-api/CMakeLists.txt
@@ -0,0 +1,36 @@
+
+set(LLVM_TARGET_DEFINITIONS ${CMAKE_CURRENT_SOURCE_DIR}/API/OffloadAPI.td)
+list(APPEND LLVM_TABLEGEN_FLAGS -I ${CMAKE_CURRENT_SOURCE_DIR}/API)
+
+tablegen(OFFLOAD offload_api.h -gen-api)
+tablegen(OFFLOAD offload_funcs.inc -gen-func-names)
+tablegen(OFFLOAD offload_impl_func_decls.inc -gen-impl-func-decls)
+tablegen(OFFLOAD offload_entry_points.inc -gen-entry-points)
+tablegen(OFFLOAD offload_print.hpp -gen-print-header)
+
+
+add_public_tablegen_target(OffloadHeaderGen)
+
+add_llvm_library(offload_new SHARED
+                src/offload_lib.cpp
+                src/offload_impl.cpp
+                DEPENDS OffloadHeaderGen
+                LINK_LIBS omptarget omptarget.rtl.cuda omptarget.rtl.amdgpu)
+
+target_include_directories(offload_new PUBLIC
+                            ${CMAKE_CURRENT_BINARY_DIR}
+                            ${CMAKE_CURRENT_BINARY_DIR}/../include
+                            ${CMAKE_CURRENT_SOURCE_DIR}/../include
+                            ${CMAKE_CURRENT_SOURCE_DIR}/../plugins-nextgen/common/include)
+
+# foreach(plugin IN LISTS LIBOMPTARGET_PLUGINS_TO_BUILD)
+#     target_link_libraries(offload_new PRIVATE omptarget.rtl.${plugin})
+# endforeach()
+
+set_target_properties(offload_new PROPERTIES
+                      POSITION_INDEPENDENT_CODE ON
+                      INSTALL_RPATH "$ORIGIN"
+                      BUILD_RPATH "$ORIGIN:${CMAKE_CURRENT_BINARY_DIR}/..")
+install(TARGETS offload_new LIBRARY COMPONENT offload_new DESTINATION "${OFFLOAD_INSTALL_LIBDIR}")
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/offload_api.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include/offload)
diff --git a/offload/new-api/README.md b/offload/new-api/README.md
new file mode 100644
index 00000000000000..95c9bf54d7badd
--- /dev/null
+++ b/offload/new-api/README.md
@@ -0,0 +1,8 @@
+# Offload New API
+
+This directory contains the implementation of the experimental work-in-progress
+new API for Offload. It builds on top of the existing plugin implementations but
+provides a single level of abstraction suitable for runtimes for languages other
+than OpenMP to be built on top of.
+
+See the [API definition readme](API/README.md) for implementation details.
\ No newline at end of file
diff --git a/offload/new-api/src/helpers.hpp b/offload/new-api/src/helpers.hpp
new file mode 100644
index 00000000000000..66148d9f210083
--- /dev/null
+++ b/offload/new-api/src/helpers.hpp
@@ -0,0 +1,96 @@
+//===- helpers.hpp- GetInfo return helpers for the new LLVM/Offload API ---===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// The getInfo*/ReturnHelper facilities provide shortcut way of writing return
+// data + size for the various getInfo APIs. Based on the equivalent
+// implementations in Unified Runtime.
+//
+//===----------------------------------------------------------------------===//
+
+#include "offload_api.h"
+
+#include <cstring>
+
+
+template <typename T, typename Assign>
+offload_result_t getInfoImpl(size_t ParamValueSize, void *ParamValue,
+                             size_t *ParamValueSizeRet, T Value,
+                             size_t ValueSize, Assign &&AssignFunc) {
+  if (!ParamValue && !ParamValueSizeRet) {
+    return OFFLOAD_RESULT_ERROR_INVALID_NULL_POINTER;
+  }
+
+  if (ParamValue != nullptr) {
+    if (ParamValueSize < ValueSize) {
+      return OFFLOAD_RESULT_ERROR_INVALID_SIZE;
+    }
+    AssignFunc(ParamValue, Value, ValueSize);
+  }
+
+  if (ParamValueSizeRet != nullptr) {
+    *ParamValueSizeRet = ValueSize;
+  }
+
+  return OFFLOAD_RESULT_SUCCESS;
+}
+
+template <typename T>
+offload_result_t getInfo(size_t ParamValueSize, void *ParamValue,
+                         size_t *ParamValueSizeRet, T Value) {
+  auto Assignment = [](void *ParamValue, T Value, size_t) {
+    *static_cast<T *>(ParamValue) = Value;
+  };
+
+  return getInfoImpl(ParamValueSize, ParamValue, ParamValueSizeRet, Value,
+                     sizeof(T), Assignment);
+}
+
+template <typename T>
+offload_result_t getInfoArray(size_t array_length, size_t ParamValueSize,
+                              void *ParamValue, size_t *ParamValueSizeRet,
+                              const T *Value) {
+  return getInfoImpl(ParamValueSize, ParamValue, ParamValueSizeRet, Value,
+                     array_length * sizeof(T), memcpy);
+}
+
+template <>
+inline offload_result_t
+getInfo<const char *>(size_t ParamValueSize, void *ParamValue,
+                      size_t *ParamValueSizeRet, const char *Value) {
+  return getInfoArray(strlen(Value) + 1, ParamValueSize, ParamValue,
+                      ParamValueSizeRet, Value);
+}
+
+class ReturnHelper {
+public:
+  ReturnHelper(size_t ParamValueSize, void *ParamValue,
+               size_t *ParamValueSizeRet)
+      : ParamValueSize(ParamValueSize), ParamValue(ParamValue),
+        ParamValueSizeRet(ParamValueSizeRet) {}
+
+  // A version where in/out info size is represented by a single pointer
+  // to a value which is updated on return
+  ReturnHelper(size_t *ParamValueSize, void *ParamValue)
+      : ParamValueSize(*ParamValueSize), ParamValue(ParamValue),
+        ParamValueSizeRet(ParamValueSize) {}
+
+  // Scalar return Value
+  template <class T> offload_result_t operator()(const T &t) {
+    return getInfo(ParamValueSize, ParamValue, ParamValueSizeRet, t);
+  }
+
+  // Array return Value
+  template <class T> offload_result_t operator()(const T *t, size_t s) {
+    return getInfoArray(s, ParamValueSize, ParamValue, ParamValueSizeRet, t);
+  }
+
+protected:
+  size_t ParamValueSize;
+  void *ParamValue;
+  size_t *ParamValueSizeRet;
+};
diff --git a/offload/new-api/src/offload_impl.cpp b/offload/new-api/src/offload_impl.cpp
new file mode 100644
index 00000000000000..165f2264ef7daa
--- /dev/null
+++ b/offload/new-api/src/offload_impl.cpp
@@ -0,0 +1,187 @@
+//===- offload_impl.cpp - Implementation of the new LLVM/Offload API ------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This contains the definitions of the new LLVM/Offload API entry points. See
+// new-api/API/README.md for more information.
+//
+//===----------------------------------------------------------------------===//
+
+#include "PluginManager.h"
+#include "helpers.hpp"
+#include "llvm/Support/FormatVariadic.h"
+#include <offload_api.h>
+
+#include <mutex>
+
+using namespace llvm;
+using namespace llvm::omp::target::plugin;
+
+// Handle type definitions. Ideally these would be 1:1 with the plugins
+struct offload_device_handle_t_ {
+  int DeviceNum;
+  GenericDeviceTy &Device;
+};
+
+struct offload_platform_handle_t_ {
+  std::unique_ptr<GenericPluginTy> Plugin;
+  std::vector<offload_device_handle_t_> Devices;
+};
+
+static std::vector<offload_platform_handle_t_> Platforms;
+
+// Every plugin exports this method to create an instance of the plugin type.
+#define PLUGIN_TARGET(Name) extern "C" GenericPluginTy *createPlugin_##Name();
+#include "Shared/Targets.def"
+
+void initPlugins() {
+  // Attempt to create an instance of each supported plugin.
+#define PLUGIN_TARGET(Name)                                                    \
+  do {                                                                         \
+    Platforms.emplace_back(offload_platform_handle_t_{                         \
+        std::unique_ptr<GenericPluginTy>(createPlugin_##Name()), {}});         \
+  } while (false);
+#include "Shared/Targets.def"
+
+  // Preemptively initialize all devices in the plugin so we can just return
+  // them from deviceGet
+  for (auto &Platform : Platforms) {
+    auto Err = Platform.Plugin->init();
+    [[maybe_unused]] std::string InfoMsg = toString(std::move(Err));
+    for (auto DevNum = 0; DevNum < Platform.Plugin->number_of_devices();
+         DevNum++) {
+      if (Platform.Plugin->init_device(DevNum) == OFFLOAD_SUCCESS) {
+        Platform.Devices.emplace_back(offload_device_handle_t_{
+            DevNum, Platform.Plugin->getDevice(DevNum)});
+      }
+    }
+  }
+}
+
+offload_result_t offloadPlatformGet_impl(uint32_t NumEntries,
+                                         offload_platform_handle_t *phPlatforms,
+                                         uint32_t *pNumPlatforms) {
+  // It is expected that offloadPlatformGet is the first function to be called.
+  // In future it may make sense to have a specific entry point for Offload
+  // initialization, or expose explicit initialization of plugins.
+  static std::once_flag InitFlag;
+  std::call_once(InitFlag, initPlugins);
+
+  if (NumEntries > Platforms.size()) {
+    return OFFLOAD_RESULT_ERROR_INVALID_SIZE;
+  }
+
+  if (phPlatforms) {
+    for (uint32_t PlatformIndex = 0; PlatformIndex < NumEntries;
+         PlatformIndex++) {
+      phPlatforms[PlatformIndex] = &Platforms[PlatformIndex];
+    }
+  }
+
+  if (pNumPlatforms) {
+    *pNumPlatforms = Platforms.size();
+  }
+
+  return OFFLOAD_RESULT_SUCCESS;
+}
+
+offload_result_t
+offloadPlatformGetInfo_impl(offload_platform_handle_t hPlatform,
+                            offload_platform_info_t propName, size_t propSize,
+                            void *pPropValue, size_t *pPropSizeRet) {
+  ReturnHelper ReturnValue(propSize, pPropValue, pPropSizeRet);
+
+  switch (propName) {
+  case OFFLOAD_PLATFORM_INFO_NAME:
+    return ReturnValue(hPlatform->Plugin->getName());
+  case OFFLOAD_PLATFORM_INFO_VENDOR_NAME:
+    // TODO: Implement this
+    return ReturnValue("Unknown platform vendor");
+  case OFFLOAD_PLATFORM_INFO_VERSION: {
+    // TODO: Implement this
+    return ReturnValue("v0.0.0");
+  }
+  case OFFLOAD_PLATFORM_INFO_BACKEND: {
+    auto PluginName = hPlatform->Plugin->getName();
+    if (PluginName == StringRef("CUDA")) {
+      return ReturnValue(OFFLOAD_PLATFORM_BACKEND_CUDA);
+    } else if (PluginName == StringRef("AMDGPU")) {
+      return ReturnValue(OFFLOAD_PLATFORM_BACKEND_AMDGPU);
+    } else {
+      return ReturnValue(OFFLOAD_PLATFORM_BACKEND_UNKNOWN);
+    }
+  }
+  default:
+    return OFFLOAD_RESULT_ERROR_INVALID_ENUMERATION;
+  }
+
+  return OFFLOAD_RESULT_SUCCESS;
+}
+
+offload_result_t offloadDeviceGet_impl(offload_platform_handle_t hPlatform,
+                                       offload_device_type_t,
+                                       uint32_t NumEntries,
+                                       offload_device_handle_t *phDevices,
+                                       uint32_t *pNumDevices) {
+
+  if (phDevices) {
+    for (uint32_t DeviceIndex = 0; DeviceIndex < NumEntries; DeviceIndex++) {
+      phDevices[DeviceIndex] = &(hPlatform->Devices[DeviceIndex]);
+    }
+  }
+
+  if (pNumDevices) {
+    *pNumDevices = static_cast<uint32_t>(hPlatform->Devices.size());
+  }
+
+  return OFFLOAD_RESULT_SUCCESS;
+}
+
+offload_result_t offloadDeviceGetInfo_impl(offload_device_handle_t hDevice,
+                                           offload_device_info_t propName,
+                                           size_t propSize, void *pPropValue,
+                                           size_t *pPropSizeRet) {
+
+  ReturnHelper ReturnValue(propSize, pPropValue, pPropSizeRet);
+
+  InfoQueueTy DevInfo;
+  if (auto Err = hDevice->Device.obtainInfoImpl(DevInfo))
+    return OFFLOAD_RESULT_ERROR_OUT_OF_RESOURCES;
+
+  // Find the info if it exists under any of the given names
+  auto GetInfo = [&DevInfo](std::vector<std::string> Names) {
+    for (auto Name : Names) {
+      auto InfoKeyMatches = [&](const InfoQueueTy::InfoQueueEntryTy &info) {
+        return info.Key == Name;
+      };
+      auto Item = std::find_if(DevInfo.getQueue().begin(),
+                               DevInfo.getQueue().end(), InfoKeyMatches);
+
+      if (Item != std::end(DevInfo.getQueue())) {
+        return Item->Value;
+      }
+    }
+
+    return std::string("");
+  };
+
+  switch (propName) {
+  case OFFLOAD_DEVICE_INFO_TYPE:
+    return ReturnValue(OFFLOAD_DEVICE_TYPE_GPU);
+  case OFFLOAD_DEVICE_INFO_NAME:
+    return ReturnValue(GetInfo({"Device Name"}).c_str());
+  case OFFLOAD_DEVICE_INFO_VENDOR:
+    return ReturnValue(GetInfo({"Vendor Name"}).c_str());
+  case OFFLOAD_DEVICE_INFO_DRIVER_VERSION:
+    return ReturnValue(
+        GetInfo({"CUDA Driver Version", "HSA Runtime Version"}).c_str());
+  default:
+    return OFFLOAD_RESULT_ERROR_UNSUPPORTED_ENUMERATION;
+  }
+
+  return OFFLOAD_RESULT_SUCCESS;
+}
diff --git a/offload/new-api/src/offload_lib.cpp b/offload/new-api/src/offload_lib.cpp
new file mode 100644
index 00000000000000..b2452b2cdab950
--- /dev/null
+++ b/offload/new-api/src/offload_lib.cpp
@@ -0,0 +1,23 @@
+//===- offload_lib.cpp - Entry points for the new LLVM/Offload API --------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file pulls in the tablegen'd API entry point functions.
+//
+//===----------------------------------------------------------------------===//
+
+#include <offload_api.h>
+#include <offload_print.hpp>
+
+#include <iostream>
+
+// Pull in the declarations for the implementation funtions. The actual entry
+// points in this file wrap these.
+#include "offload_impl_func_decls.inc"
+
+// Pull in the tablegen'd entry point definitions.
+#include "offload_entry_points.inc"
diff --git a/offload/plugins-nextgen/common/include/PluginInterface.h b/offload/plugins-nextgen/common/include/PluginInterface.h
index 41cc0f286a581f..76dde94f03d422 100644
--- a/offload/plugins-nextgen/common/include/PluginInterface.h
+++ b/offload/plugins-nextgen/common/include/PluginInterface.h
@@ -124,6 +124,7 @@ enum InfoLevelKind { InfoLevel1 = 1, InfoLevel2, InfoLevel3 };
 /// we use the level to determine the indentation of the key-value property at
 /// printing time. See the enum InfoLevelKind for the list of accepted levels.
 class InfoQueueTy {
+public:
   struct InfoQueueEntryTy {
     std::string Key;
     std::string Value;
@@ -131,6 +132,8 @@ class InfoQueueTy {
     uint64_t Level;
   };
 
+
+private: // TODO move
   std::deque<InfoQueueEntryTy> Queue;
 
 public:
@@ -153,6 +156,10 @@ class InfoQueueTy {
       Queue.push_back({Key, Value, Units, L});
   }
 
+  const std::deque<InfoQueueEntryTy> &getQueue() const {
+    return Queue;
+  }
+
   /// Print all info entries added to the queue.
   void print() const {
     // We print four spances for each level.
diff --git a/offload/tools/offload-tblgen/APIGen.cpp b/offload/tools/offload-tblgen/APIGen.cpp
new file mode 100644
index 00000000000000..6fdf3025f25bb5
--- /dev/null
+++ b/offload/tools/offload-tblgen/APIGen.cpp
@@ -0,0 +1,196 @@
+//===- offload-tblgen/APIGen.cpp - Tablegen backend for Offload header ----===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This is a Tablegen backend that produces the contents of the Offload API
+// header. The generated comments are Doxygen compatible.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/TableGen/Record.h"
+#include "llvm/TableGen/TableGenBackend.h"
+
+#include "GenCommon.hpp"
+#include "RecordTypes.hpp"
+
+using namespace llvm;
+using namespace offload::tblgen;
+
+// Produce a possibly multi-line comment from the input string
+static std::string MakeComment(StringRef in) {
+  std::string out = "";
+  size_t LineStart = 0;
+  size_t LineBreak = 0;
+  while (LineBreak < in.size()) {
+    LineBreak = in.find_first_of("\n", LineStart);
+    if (LineBreak - LineStart <= 1) {
+      break;
+    }
+    out += std::string("\t///< ") +
+           in.substr(LineStart, LineBreak - LineStart).str() + "\n";
+    LineStart = LineBreak + 1;
+  }
+
+  return out;
+}
+
+static void ProcessHandle(const HandleRec &H, raw_ostream &OS) {
+  OS << CommentsHeader;
+  OS << formatv("/// @brief {0}\n", H.getDesc());
+  OS << formatv("typedef struct {0}_ *{0};\n", H.getName());
+}
+
+static void ProcessTypedef(const TypedefRec &T, raw_ostream &OS) {
+  OS << CommentsHeader;
+  OS << formatv("/// @brief {0}\n", T.getDesc());
+  OS << formatv("typedef {0} {1};\n", T.getValue(), T.getName());
+}
+
+static void ProcessMacro(const MacroRec &M, raw_ostream &OS) {
+  OS << CommentsHeader;
+  OS << formatv("#ifndef {0}\n", M.getName());
+  if (auto Condition = M.getCondition()) {
+    OS << formatv("#if {0}\n", *Condition);
+  }
+  OS << "/// @brief " << M.getDesc() << "\n";
+  OS << formatv("#define {0} {1}\n", M.getNameWithArgs(), M.getValue());
+  if (auto AltValue = M.getAltValue()) {
+    OS << "#else\n";
+    OS << formatv("#define {0} {1}\n", M.getNameWithArgs(), *AltValue);
+  }
+  if (auto Condition = M.getCondition()) {
+    OS << formatv("#endif // {0}\n", *Condition);
+  }
+  OS << formatv("#endif // {0}\n", M.getName());
+}
+
+static void ProcessFunction(const FunctionRec &F, raw_ostream &OS) {
+  OS << CommentsHeader;
+  OS << formatv("/// @brief {0}\n", F.getDesc());
+  OS << CommentsBreak;
+
+  OS << "/// @details\n";
+  for (auto &Detail : F.getDetails()) {
+    OS << formatv("///    - {0}\n", Detail);
+  }
+  OS << CommentsBreak;
+
+  // Emit analogue remarks
+  auto Analogues = F.getAnalogues();
+  if (!Analogues.empty()) {
+    OS << "/// @remarks\n///  _Analogues_\n";
+    for (auto &Analogue : Analogues) {
+      OS << formatv("///    - **{0}**\n", Analogue);
+    }
+    OS << CommentsBreak;
+  }
+
+  OS << "/// @returns\n";
+  auto Returns = F.getReturns();
+  for (auto &Ret : Returns) {
+    OS << formatv("///     - ::{0}\n", Ret.getValue());
+    auto RetConditions = Ret.getConditions();
+    for (auto &RetCondition : RetConditions) {
+      OS << formatv("///         + {0}\n", RetCondition);
+    }
+  }
+
+  OS << formatv("{0}_APIEXPORT {1}_result_t {0}_APICALL ", PrefixUpper,
+                PrefixLower);
+  OS << F.getName();
+  OS << "(\n";
+  auto Params = F.getParams();
+  for (auto &Param : Params) {
+    OS << "  " << Param.getType() << " " << Param.getName();
+    if (Param != Params.back()) {
+      OS << ", ";
+    } else {
+      OS << " ";
+    }
+    OS << MakeParamComment(Param) << "\n";
+  }
+  OS << ");\n\n";
+}
+
+static void ProcessEnum(const EnumRec &Enum, raw_ostream &OS) {
+  OS << CommentsHeader;
+  OS << formatv("/// @brief {0}\n", Enum.getDesc());
+  OS << formatv("typedef enum {0} {{\n", Enum.getName());
+
+  uint32_t EtorVal = 0;
+  for (const auto &EnumVal : Enum.getValues()) {
+    auto Desc = MakeComment(EnumVal.getDesc());
+    OS << formatv(TAB_1 "{0}_{1} = {2}, {3}", Enum.getEnumValNamePrefix(),
+                  EnumVal.getName(), EtorVal++, Desc);
+  }
+
+  // Add force uint32 val
+  OS << formatv(TAB_1 "/// @cond\n" TAB_1
+                      "{0}_FORCE_UINT32 = 0x7fffffff\n" TAB_1
+                      "/// @endcond\n\n",
+                Enum.getEnumValNamePrefix());
+
+  OS << formatv("} {0};\n", Enum.getName());
+}
+
+static void ProcessStruct(const StructRec &Struct, raw_ostream &OS) {
+  OS << CommentsHeader;
+  OS << formatv("/// @brief {0}\n", Struct.getDesc());
+  OS << formatv("typedef struct {0} {{\n", Struct.getName());
+
+  for (const auto &Member : Struct.getMembers()) {
+    OS << formatv(TAB_1 "{0} {1}; {2}", Member.getType(), Member.getName(),
+                  MakeComment(Member.getDesc()));
+  }
+
+  OS << formatv("} {0};\n\n", Struct.getName());
+}
+
+static void ProcessFuncParamStruct(const FunctionRec &Func, raw_ostream &OS) {
+  auto FuncParamStructBegin = R"(
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Function parameters for {0}
+/// @details Each entry is a pointer to the parameter passed to the function;
+typedef struct {1} {{
+)";
+
+  OS << formatv(FuncParamStructBegin, Func.getName(),
+                Func.getParamStructName());
+  for (const auto &Param : Func.getParams()) {
+    OS << TAB_1 << Param.getType() << "* p" << Param.getName() << ";\n";
+  }
+  OS << formatv("} {0};\n", Func.getParamStructName());
+}
+
+void EmitOffloadAPI(RecordKeeper &Records, raw_ostream &OS) {
+  OS << FileHeader;
+  // Generate main API definitions
+  for (auto *R : Records.getAllDerivedDefinitions("APIObject")) {
+    if (R->isSubClassOf("Macro")) {
+      ProcessMacro(MacroRec{R}, OS);
+    } else if (R->isSubClassOf("Typedef")) {
+      ProcessTypedef(TypedefRec{R}, OS);
+    } else if (R->isSubClassOf("Handle")) {
+      ProcessHandle(HandleRec{R}, OS);
+    } else if (R->isSubClassOf("Function")) {
+      ProcessFunction(FunctionRec{R}, OS);
+    } else if (R->isSubClassOf("Enum")) {
+      ProcessEnum(EnumRec{R}, OS);
+    } else if (R->isSubClassOf("Struct")) {
+      ProcessStruct(StructRec{R}, OS);
+    }
+  }
+
+  // Generate auxiliary definitions (func param structs etc)
+  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
+    ProcessFuncParamStruct(FunctionRec{R}, OS);
+  }
+
+  OS << FileFooter;
+}
diff --git a/offload/tools/offload-tblgen/CMakeLists.txt b/offload/tools/offload-tblgen/CMakeLists.txt
new file mode 100644
index 00000000000000..52986cbbaa9187
--- /dev/null
+++ b/offload/tools/offload-tblgen/CMakeLists.txt
@@ -0,0 +1,24 @@
+##===----------------------------------------------------------------------===##
+#
+# 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(TableGen)
+
+add_tablegen(offload-tblgen OFFLOAD
+  EXPORT OFFLOAD
+  APIGen.cpp
+  EntryPointGen.cpp
+  FuncsGen.cpp
+  GenCommon.hpp
+  Generators.hpp
+  offload-tblgen.cpp
+  PrintGen.cpp
+  RecordTypes.hpp
+  )
+
+set(OFFLOAD_TABLEGEN_EXE "${OFFLOAD_TABLEGEN_EXE}" CACHE INTERNAL "")
+set(OFFLOAD_TABLEGEN_TARGET "${OFFLOAD_TABLEGEN_TARGET}" CACHE INTERNAL "")
+
diff --git a/offload/tools/offload-tblgen/EntryPointGen.cpp b/offload/tools/offload-tblgen/EntryPointGen.cpp
new file mode 100644
index 00000000000000..b89592187639e3
--- /dev/null
+++ b/offload/tools/offload-tblgen/EntryPointGen.cpp
@@ -0,0 +1,86 @@
+//===- offload-tblgen/EntryPointGen.cpp - Tablegen backend for Offload ----===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This is a Tablegen backend that produces the actual entry points for the
+// Offload API. It serves as a place to integrate functionality like tracing
+// and validation before dispatching to the actual implementations.
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/TableGen/Record.h"
+
+#include "GenCommon.hpp"
+#include "RecordTypes.hpp"
+
+using namespace llvm;
+using namespace offload::tblgen;
+
+static void EmitEntryPointFunc(const FunctionRec &F, raw_ostream &OS) {
+  OS << CommentsHeader;
+  // Emit preamble
+  OS << formatv("{1}_APIEXPORT {0}_result_t {1}_APICALL {2}(\n  ", PrefixLower,
+                PrefixUpper, F.getName());
+  // Emit arguments
+  std::string ParamNameList = "";
+  for (auto &Param : F.getParams()) {
+    OS << Param.getType() << " " << Param.getName();
+    if (Param != F.getParams().back()) {
+      OS << ", ";
+    }
+    ParamNameList += Param.getName().str() + ", ";
+  }
+  OS << ") {\n";
+
+  OS << TAB_1 "if (true /*enableParameterValidation*/) {\n";
+
+  // Emit validation checks
+  for (const auto &Return : F.getReturns()) {
+    for (auto &Condition : Return.getConditions()) {
+      if (Condition.starts_with("`") && Condition.ends_with("`")) {
+        auto ConditionString = Condition.substr(1, Condition.size() - 2);
+        OS << formatv(TAB_2 "if ({0}) {{\n", ConditionString);
+        OS << formatv(TAB_3 "return {0};\n", Return.getValue());
+        OS << TAB_2 "}\n\n";
+      }
+    }
+  }
+  OS << "  }\n\n";
+
+  // Emit pre-call prints
+  OS << TAB_1 "if (std::getenv(\"OFFLOAD_TRACE\")) {\n";
+  OS << formatv(TAB_2 "std::cout << \"---> {0}\";\n", F.getName());
+  OS << TAB_1 "}\n\n";
+
+  //   Perform actual function call
+  ParamNameList = ParamNameList.substr(0, ParamNameList.size() - 2);
+  OS << formatv(TAB_1 "{0}_result_t result = {1}_impl({2});\n\n", PrefixLower,
+                F.getName(), ParamNameList);
+
+  // Emit post-call prints
+  OS << TAB_1 "if (std::getenv(\"OFFLOAD_TRACE\")) {\n";
+  OS << formatv(TAB_2 "{0} Params = {{ ", F.getParamStructName());
+  for (const auto &Param : F.getParams()) {
+    OS << "&" << Param.getName();
+    if (Param != F.getParams().back()) {
+      OS << ", ";
+    }
+  }
+  OS << formatv("};\n");
+  OS << TAB_2 "std::cout << \"(\" << &Params << \")\";\n";
+  OS << TAB_2 "std::cout << \"-> \" << result << \"\\n\";\n";
+  OS << TAB_1 "}\n";
+
+  OS << TAB_1 "return result;\n";
+  OS << "}\n";
+}
+
+void EmitOffloadEntryPoints(RecordKeeper &Records, raw_ostream &OS) {
+  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
+    EmitEntryPointFunc(FunctionRec{R}, OS);
+  }
+}
diff --git a/offload/tools/offload-tblgen/FuncsGen.cpp b/offload/tools/offload-tblgen/FuncsGen.cpp
new file mode 100644
index 00000000000000..4dc74047ac49d8
--- /dev/null
+++ b/offload/tools/offload-tblgen/FuncsGen.cpp
@@ -0,0 +1,54 @@
+//===- offload-tblgen/APIGen.cpp - Tablegen backend for Offload validation ===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This is a Tablegen backend that produces validation functions for the Offload
+// API entry point functions.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/TableGen/Record.h"
+
+#include "GenCommon.hpp"
+#include "RecordTypes.hpp"
+
+using namespace llvm;
+using namespace offload::tblgen;
+
+// Emit a list of just the API function names
+void EmitOffloadFuncNames(RecordKeeper &Records, raw_ostream &OS) {
+  OS << R"(
+#ifndef OFFLOAD_FUNC
+#error Please define the macro OFFLOAD_FUNC(Function)
+#endif
+
+)";
+  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
+    FunctionRec FR{R};
+    OS << formatv("OFFLOAD_FUNC({0})", FR.getName()) << "\n";
+  }
+
+  OS << "\n#undef OFFLOAD_FUNC\n";
+}
+
+void EmitOffloadImplFuncDecls(RecordKeeper &Records, raw_ostream &OS) {
+  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
+    FunctionRec F{R};
+    OS << formatv("{0}_result_t {1}_impl(", PrefixLower, F.getName());
+    auto Params = F.getParams();
+    for (auto &Param : Params) {
+      OS << Param.getType() << " " << Param.getName();
+      if (Param != Params.back()) {
+        OS << ", ";
+      } else {
+        OS << ");";
+      }
+    }
+    OS << "\n";
+  }
+}
diff --git a/offload/tools/offload-tblgen/GenCommon.hpp b/offload/tools/offload-tblgen/GenCommon.hpp
new file mode 100644
index 00000000000000..fb24ef06f9ec8f
--- /dev/null
+++ b/offload/tools/offload-tblgen/GenCommon.hpp
@@ -0,0 +1,58 @@
+//===- offload-tblgen/GenCommon.cpp - Common defs for Offload generators --===//
+//
+// 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 "RecordTypes.hpp"
+#include "llvm/Support/FormatVariadic.h"
+
+// Having inline bits of tabbed code is hard to read, provide some definitions
+// so we can keep things tidier
+#define TAB_1 "  "
+#define TAB_2 "    "
+#define TAB_3 "      "
+#define TAB_4 "        "
+#define TAB_5 "          "
+
+constexpr auto FileHeader = R"(
+// Auto-generated file, do not manually edit.
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+)";
+
+constexpr auto FileFooter = R"(
+#if defined(__cplusplus)
+} // extern "C"
+#endif
+
+)";
+
+constexpr auto CommentsHeader = R"(
+///////////////////////////////////////////////////////////////////////////////
+)";
+
+constexpr auto CommentsBreak = "///\n";
+
+constexpr auto PrefixLower = "offload";
+constexpr auto PrefixUpper = "OFFLOAD";
+
+inline std::string
+MakeParamComment(const llvm::offload::tblgen::ParamRec &Param) {
+  return llvm::formatv("///< {0}{1}{2} {3}", (Param.isIn() ? "[in]" : ""),
+                       (Param.isOut() ? "[out]" : ""),
+                       (Param.isOpt() ? "[optional]" : ""), Param.getDesc());
+}
+
diff --git a/offload/tools/offload-tblgen/Generators.hpp b/offload/tools/offload-tblgen/Generators.hpp
new file mode 100644
index 00000000000000..4b300f52ddf84d
--- /dev/null
+++ b/offload/tools/offload-tblgen/Generators.hpp
@@ -0,0 +1,18 @@
+//===- offload-tblgen/Generators.hpp - Offload generator declarations -----===//
+//
+// 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 "llvm/TableGen/Record.h"
+
+void EmitOffloadAPI(llvm::RecordKeeper &Records, llvm::raw_ostream &OS);
+void EmitOffloadFuncNames(llvm::RecordKeeper &Records, llvm::raw_ostream &OS);
+void EmitOffloadImplFuncDecls(llvm::RecordKeeper &Records,
+                              llvm::raw_ostream &OS);
+void EmitOffloadEntryPoints(llvm::RecordKeeper &Records, llvm::raw_ostream &OS);
+void EmitOffloadPrintHeader(llvm::RecordKeeper &Records, llvm::raw_ostream &OS);
diff --git a/offload/tools/offload-tblgen/PrintGen.cpp b/offload/tools/offload-tblgen/PrintGen.cpp
new file mode 100644
index 00000000000000..0c47695eca12b6
--- /dev/null
+++ b/offload/tools/offload-tblgen/PrintGen.cpp
@@ -0,0 +1,206 @@
+//===- offload-tblgen/APIGen.cpp - Tablegen backend for Offload printing --===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This is a Tablegen backend that produces print functions for the Offload API
+// entry point functions.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/TableGen/Record.h"
+
+#include "GenCommon.hpp"
+#include "RecordTypes.hpp"
+
+using namespace llvm;
+using namespace offload::tblgen;
+
+constexpr auto PrintEnumHeader =
+    R"(///////////////////////////////////////////////////////////////////////////////
+/// @brief Print operator for the {0} type
+/// @returns std::ostream &
+)";
+
+constexpr auto PrintTaggedEnumHeader =
+    R"(///////////////////////////////////////////////////////////////////////////////
+/// @brief Print type-tagged {0} enum value
+/// @returns std::ostream &
+)";
+
+static void ProcessEnum(const EnumRec &Enum, raw_ostream &OS) {
+  OS << formatv(PrintEnumHeader, Enum.getName());
+  OS << formatv(
+      "inline std::ostream &operator<<(std::ostream &os, enum {0} value) "
+      "{{\n" TAB_1 "switch (value) {{\n",
+      Enum.getName());
+
+  for (const auto &Val : Enum.getValues()) {
+    auto Name = Enum.getEnumValNamePrefix() + "_" + Val.getName();
+    OS << formatv(TAB_1 "case {0}:\n", Name);
+    OS << formatv(TAB_2 "os << \"{0}\";\n", Name);
+    OS << formatv(TAB_2 "break;\n");
+  }
+
+  OS << TAB_1 "default:\n" TAB_2 "os << \"unknown enumerator\";\n" TAB_2
+              "break;\n" TAB_1 "}\n" TAB_1 "return os;\n}\n\n";
+
+  if (!Enum.isTyped()) {
+    return;
+  }
+
+  OS << formatv(PrintTaggedEnumHeader, Enum.getName());
+
+  OS << formatv(R"""(template <>
+inline void printTagged(std::ostream &os, const void *ptr, {0} value, size_t size) {{
+  if (ptr == NULL) {{
+    printPtr(os, ptr);
+    return;
+  }
+
+  switch (value) {{
+)""",
+                Enum.getName());
+
+  for (const auto &Val : Enum.getValues()) {
+    auto Name = Enum.getEnumValNamePrefix() + "_" + Val.getName();
+    auto Type = Val.getTaggedType();
+    OS << formatv(TAB_1 "case {0}: {{\n", Name);
+    // Special case for strings
+    if (Type == "char[]") {
+      OS << formatv(TAB_2 "printPtr(os, (const char*) ptr);\n");
+    } else {
+      OS << formatv(TAB_2 "const {0} * const tptr = (const {0} * const)ptr;\n",
+                    Type);
+      // TODO: Handle other cases here
+      OS << TAB_2 "os << (const void *)tptr << \" (\";\n";
+      if (Type.ends_with("*")) {
+        OS << TAB_2 "os << printPtr(os, tptr);\n";
+      } else {
+        OS << TAB_2 "os << *tptr;\n";
+      }
+      OS << TAB_2 "os << \")\";\n";
+    }
+    OS << formatv(TAB_2 "break;\n" TAB_1 "}\n");
+  }
+
+  OS << TAB_1 "default:\n" TAB_2 "os << \"unknown enumerator\";\n" TAB_2
+              "break;\n" TAB_1 "}\n";
+
+  OS << "}\n";
+}
+
+static void EmitFunctionParamStructPrint(const FunctionRec &Func,
+                                         raw_ostream &OS) {
+  OS << formatv(R"(
+inline std::ostream &operator<<(std::ostream &os, [[maybe_unused]] const struct {0} *params) {{
+)",
+                Func.getParamStructName());
+
+  for (const auto &Param : Func.getParams()) {
+    OS << formatv(TAB_1 "os << \".{0} = \";\n", Param.getName());
+    if (auto Range = Param.getRange()) {
+      OS << formatv(TAB_1 "os << \"{{\";\n");
+      OS << formatv(TAB_1 "for (size_t i = {0}; i < *params->p{1}; i++){{\n",
+                    Range->first, Range->second);
+      OS << TAB_2 "if (i > 0) {\n";
+      OS << TAB_3 " os << \", \";\n";
+      OS << TAB_2 "}\n";
+      OS << formatv(TAB_2 "printPtr(os, (*params->p{0})[i]);\n",
+                    Param.getName());
+      OS << formatv(TAB_1 "}\n");
+      OS << formatv(TAB_1 "os << \"}\";\n");
+    } else if (auto TypeInfo = Param.getTypeInfo()) {
+      OS << formatv(
+          TAB_1
+          "printTagged(os, *params->p{0}, *params->p{1}, *params->p{2});\n",
+          Param.getName(), TypeInfo->first, TypeInfo->second);
+    } else if (Param.isPointerType() || Param.isHandleType()) {
+      OS << formatv(TAB_1 "printPtr(os, *params->p{0});\n", Param.getName());
+    } else {
+      OS << formatv(TAB_1 "os << *params->p{0};\n", Param.getName());
+    }
+    if (Param != Func.getParams().back()) {
+      OS << TAB_1 "os << \", \";\n";
+    }
+  }
+
+  OS << TAB_1 "return os;\n}\n";
+}
+
+void EmitOffloadPrintHeader(RecordKeeper &Records, raw_ostream &OS) {
+  OS << R"""(
+// Auto-generated file, do not manually edit.
+
+#pragma once
+
+#include <offload_api.h>
+#include <ostream>
+
+
+template <typename T> inline offload_result_t printPtr(std::ostream &os, const T *ptr);
+template <typename T> inline void printTagged(std::ostream &os, const void *ptr, T value, size_t size);
+)""";
+
+  // ==========
+  OS << "template <typename T> struct is_handle : std::false_type {};\n";
+  for (auto *R : Records.getAllDerivedDefinitions("Handle")) {
+    HandleRec H{R};
+    OS << formatv("template <> struct is_handle<{0}> : std::true_type {{};\n",
+                  H.getName());
+  }
+  OS << "template <typename T> inline constexpr bool is_handle_v = "
+        "is_handle<T>::value;\n";
+  // =========
+
+  // Forward declare the operator<< overloads so their implementations can
+  // use each other.
+  OS << "\n";
+  for (auto *R : Records.getAllDerivedDefinitions("Enum")) {
+    OS << formatv(
+        "inline std::ostream &operator<<(std::ostream &os, enum {0} value);\n",
+        EnumRec{R}.getName());
+  }
+  OS << "\n";
+
+  // Create definitions
+  for (auto *R : Records.getAllDerivedDefinitions("Enum")) {
+    EnumRec E{R};
+    ProcessEnum(E, OS);
+  }
+
+  // Emit print functions for the function param structs
+  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
+    EmitFunctionParamStructPrint(FunctionRec{R}, OS);
+  }
+
+  OS << R"""(
+///////////////////////////////////////////////////////////////////////////////
+// @brief Print pointer value
+template <typename T> inline offload_result_t printPtr(std::ostream &os, const T *ptr) {
+    if (ptr == nullptr) {
+        os << "nullptr";
+    } else if constexpr (std::is_pointer_v<T>) {
+        os << (const void *)(ptr) << " (";
+        printPtr(os, *ptr);
+        os << ")";
+    } else if constexpr (std::is_void_v<T> || is_handle_v<T *>) {
+        os << (const void *)ptr;
+    } else if constexpr (std::is_same_v<std::remove_cv_t< T >, char>) {
+        os << (const void *)(ptr) << " (";
+        os << ptr;
+        os << ")";
+    } else {
+        os << (const void *)(ptr) << " (";
+        os << *ptr;
+        os << ")";
+    }
+
+    return OFFLOAD_RESULT_SUCCESS;
+}
+  )""";
+}
diff --git a/offload/tools/offload-tblgen/RecordTypes.hpp b/offload/tools/offload-tblgen/RecordTypes.hpp
new file mode 100644
index 00000000000000..ccd45d472cf770
--- /dev/null
+++ b/offload/tools/offload-tblgen/RecordTypes.hpp
@@ -0,0 +1,227 @@
+//===- offload-tblgen/RecordTypes.cpp - Offload record type wrappers -----===-//
+//
+// 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 <string>
+
+#include "llvm/TableGen/Record.h"
+
+namespace llvm {
+namespace offload {
+namespace tblgen {
+
+class HandleRec {
+public:
+  explicit HandleRec(Record *rec) : rec(rec) {}
+  StringRef getName() const { return rec->getValueAsString("name"); }
+  StringRef getDesc() const { return rec->getValueAsString("desc"); }
+
+private:
+  Record *rec;
+};
+
+class MacroRec {
+public:
+  explicit MacroRec(Record *rec) : rec(rec) {
+    auto Name = rec->getValueAsString("name");
+    auto OpenBrace = Name.find_first_of("(");
+    nameWithoutArgs = Name.substr(0, OpenBrace);
+  }
+  StringRef getName() const { return nameWithoutArgs; }
+  StringRef getNameWithArgs() const { return rec->getValueAsString("name"); }
+  StringRef getDesc() const { return rec->getValueAsString("desc"); }
+
+  std::optional<StringRef> getCondition() const {
+    return rec->getValueAsOptionalString("condition");
+  }
+  StringRef getValue() const { return rec->getValueAsString("value"); }
+  std::optional<StringRef> getAltValue() const {
+    return rec->getValueAsOptionalString("alt_value");
+  }
+
+private:
+  Record *rec;
+  std::string nameWithoutArgs;
+};
+
+class TypedefRec {
+public:
+  explicit TypedefRec(Record *rec) : rec(rec) {}
+  StringRef getName() const { return rec->getValueAsString("name"); }
+  StringRef getDesc() const { return rec->getValueAsString("desc"); }
+  StringRef getValue() const { return rec->getValueAsString("value"); }
+
+private:
+  Record *rec;
+};
+
+class EnumValueRec {
+public:
+  explicit EnumValueRec(Record *rec) : rec(rec) {}
+  std::string getName() const { return rec->getValueAsString("name").upper(); }
+  StringRef getDesc() const { return rec->getValueAsString("desc"); }
+  StringRef getTaggedType() const {
+    return rec->getValueAsString("tagged_type");
+  }
+
+private:
+  Record *rec;
+};
+
+class EnumRec {
+public:
+  explicit EnumRec(Record *rec) : rec(rec) {
+    for (auto *Val : rec->getValueAsListOfDefs("etors")) {
+      vals.emplace_back(EnumValueRec{Val});
+    }
+  }
+  StringRef getName() const { return rec->getValueAsString("name"); }
+  StringRef getDesc() const { return rec->getValueAsString("desc"); }
+  const std::vector<EnumValueRec> &getValues() const { return vals; }
+
+  std::string getEnumValNamePrefix() const {
+    return StringRef(getName().str().substr(0, getName().str().length() - 2))
+        .upper();
+  }
+
+  bool isTyped() const { return rec->getValueAsBit("is_typed"); }
+
+private:
+  Record *rec;
+  std::vector<EnumValueRec> vals;
+};
+
+class StructMemberRec {
+public:
+  explicit StructMemberRec(Record *rec) : rec(rec) {}
+  StringRef getType() const { return rec->getValueAsString("type"); }
+  StringRef getName() const { return rec->getValueAsString("name"); }
+  StringRef getDesc() const { return rec->getValueAsString("desc"); }
+
+private:
+  Record *rec;
+};
+
+class StructRec {
+public:
+  explicit StructRec(Record *rec) : rec(rec) {
+    for (auto *Member : rec->getValueAsListOfDefs("all_members")) {
+      members.emplace_back(StructMemberRec(Member));
+    }
+  }
+  StringRef getName() const { return rec->getValueAsString("name"); }
+  StringRef getDesc() const { return rec->getValueAsString("desc"); }
+  std::optional<StringRef> getBaseClass() const {
+    return rec->getValueAsOptionalString("base_class");
+  }
+  const std::vector<StructMemberRec> &getMembers() const { return members; }
+
+private:
+  Record *rec;
+  std::vector<StructMemberRec> members;
+};
+
+class ParamRec {
+public:
+  explicit ParamRec(Record *rec) : rec(rec) {
+    flags = rec->getValueAsBitsInit("flags");
+    auto *Range = rec->getValueAsDef("range");
+    auto RangeBegin = Range->getValueAsString("begin");
+    auto RangeEnd = Range->getValueAsString("end");
+    if (RangeBegin != "" && RangeEnd != "") {
+      range = {RangeBegin, RangeEnd};
+    } else {
+      range = std::nullopt;
+    }
+
+    auto *TypeInfo = rec->getValueAsDef("type_info");
+    auto TypeInfoEnum = TypeInfo->getValueAsString("enum");
+    auto TypeInfoSize = TypeInfo->getValueAsString("size");
+    if (TypeInfoEnum != "" && TypeInfoSize != "") {
+      typeinfo = {TypeInfoEnum, TypeInfoSize};
+    } else {
+      typeinfo = std::nullopt;
+    }
+  }
+  StringRef getName() const { return rec->getValueAsString("name"); }
+  StringRef getType() const { return rec->getValueAsString("type"); }
+  bool isPointerType() const { return getType().ends_with('*'); }
+  bool isHandleType() const { return getType().ends_with("_handle_t"); }
+  StringRef getDesc() const { return rec->getValueAsString("desc"); }
+  bool isIn() const { return dyn_cast<BitInit>(flags->getBit(0))->getValue(); }
+  bool isOut() const { return dyn_cast<BitInit>(flags->getBit(1))->getValue(); }
+  bool isOpt() const { return dyn_cast<BitInit>(flags->getBit(2))->getValue(); }
+
+  Record *getRec() const { return rec; }
+  std::optional<std::pair<StringRef, StringRef>> getRange() const {
+    return range;
+  }
+
+  std::optional<std::pair<StringRef, StringRef>> getTypeInfo() const {
+    return typeinfo;
+  }
+
+  // Needed to check whether we're at the back of a vector of params
+  bool operator!=(const ParamRec &p) const { return rec != p.getRec(); }
+
+private:
+  Record *rec;
+  BitsInit *flags;
+  std::optional<std::pair<StringRef, StringRef>> range;
+  std::optional<std::pair<StringRef, StringRef>> typeinfo;
+};
+
+class ReturnRec {
+public:
+  ReturnRec(Record *rec) : rec(rec) {}
+  StringRef getValue() const { return rec->getValueAsString("value"); }
+  std::vector<StringRef> getConditions() const {
+    return rec->getValueAsListOfStrings("conditions");
+  }
+
+private:
+  Record *rec;
+};
+
+class FunctionRec {
+public:
+  FunctionRec(Record *rec) : rec(rec) {
+    for (auto &Ret : rec->getValueAsListOfDefs("all_returns"))
+      rets.emplace_back(Ret);
+    for (auto &Param : rec->getValueAsListOfDefs("params"))
+      params.emplace_back(Param);
+  }
+
+  std::string getParamStructName() const {
+    return llvm::formatv("{0}_params_t",
+                         llvm::convertToSnakeFromCamelCase(getName()));
+  }
+
+  StringRef getName() const { return rec->getValueAsString("name"); }
+  StringRef getClass() const { return rec->getValueAsString("api_class"); }
+  const std::vector<ReturnRec> &getReturns() const { return rets; }
+  const std::vector<ParamRec> &getParams() const { return params; }
+  StringRef getDesc() const { return rec->getValueAsString("desc"); }
+  std::vector<StringRef> getDetails() const {
+    return rec->getValueAsListOfStrings("details");
+  }
+  std::vector<StringRef> getAnalogues() const {
+    return rec->getValueAsListOfStrings("analogues");
+  }
+
+private:
+  std::vector<ReturnRec> rets;
+  std::vector<ParamRec> params;
+
+  Record *rec;
+};
+
+} // namespace tblgen
+} // namespace offload
+} // namespace llvm
diff --git a/offload/tools/offload-tblgen/offload-tblgen.cpp b/offload/tools/offload-tblgen/offload-tblgen.cpp
new file mode 100644
index 00000000000000..2e11ec08d0e93a
--- /dev/null
+++ b/offload/tools/offload-tblgen/offload-tblgen.cpp
@@ -0,0 +1,95 @@
+//===- offload-tblgen/offload-tblgen.cpp ----------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This is a Tablegen tool that produces source files for the Offload project.
+// See offload/API/README.md for more information.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/InitLLVM.h"
+#include "llvm/TableGen/Main.h"
+#include "llvm/TableGen/Record.h"
+
+#include "Generators.hpp"
+
+namespace llvm {
+namespace offload {
+namespace tblgen {
+
+enum ActionType {
+  PrintRecords,
+  DumpJSON,
+  GenAPI,
+  GenFuncNames,
+  GenImplFuncDecls,
+  GenEntryPoints,
+  GenPrintHeader
+};
+
+namespace {
+cl::opt<ActionType> Action(
+    cl::desc("Action to perform:"),
+    cl::values(
+        clEnumValN(PrintRecords, "print-records",
+                   "Print all records to stdout (default)"),
+        clEnumValN(DumpJSON, "dump-json",
+                   "Dump all records as machine-readable JSON"),
+        clEnumValN(GenAPI, "gen-api", "Generate Offload API header contents"),
+        clEnumValN(GenFuncNames, "gen-func-names",
+                   "Generate a list of all Offload API function names"),
+        clEnumValN(
+            GenImplFuncDecls, "gen-impl-func-decls",
+            "Generate declarations for Offload API implementation functions"),
+        clEnumValN(GenEntryPoints, "gen-entry-points",
+                   "Generate Offload API wrapper function definitions"),
+        clEnumValN(GenPrintHeader, "gen-print-header",
+                   "Generate Offload API print header")));
+}
+
+static bool OffloadTableGenMain(raw_ostream &OS, RecordKeeper &Records) {
+  switch (Action) {
+  case PrintRecords:
+    OS << Records;
+    break;
+  case DumpJSON:
+    EmitJSON(Records, OS);
+    break;
+  case GenAPI:
+    EmitOffloadAPI(Records, OS);
+    break;
+  case GenFuncNames:
+    EmitOffloadFuncNames(Records, OS);
+    break;
+  case GenImplFuncDecls:
+    EmitOffloadImplFuncDecls(Records, OS);
+    break;
+  case GenEntryPoints:
+    EmitOffloadEntryPoints(Records, OS);
+    break;
+  case GenPrintHeader:
+    EmitOffloadPrintHeader(Records, OS);
+    break;
+  }
+
+  return false;
+}
+
+int OffloadTblgenMain(int argc, char **argv) {
+  InitLLVM y(argc, argv);
+  cl::ParseCommandLineOptions(argc, argv);
+  return TableGenMain(argv[0], &OffloadTableGenMain);
+}
+} // namespace tblgen
+} // namespace offload
+} // namespace llvm
+
+using namespace llvm;
+using namespace offload::tblgen;
+
+int main(int argc, char **argv) { return OffloadTblgenMain(argc, argv); }



More information about the llvm-commits mailing list