diff --git a/offload/CMakeLists.txt b/offload/CMakeLists.txt
index dfd25bad60843..af2e07d514721 100644
--- a/offload/CMakeLists.txt
+++ b/offload/CMakeLists.txt
@@ -351,6 +351,9 @@ add_subdirectory(tools)
 # Build target agnostic offloading library.
 add_subdirectory(src)
 
+add_subdirectory(tools/offload-tblgen)
+add_subdirectory(liboffload)
+
 # Add tests.
 add_subdirectory(test)
 
diff --git a/offload/cmake/OpenMPTesting.cmake b/offload/cmake/OpenMPTesting.cmake
index 6609d6301d0f9..f97def2c52eba 100644
--- a/offload/cmake/OpenMPTesting.cmake
+++ b/offload/cmake/OpenMPTesting.cmake
@@ -48,6 +48,17 @@ function(find_standalone_test_dependencies)
     return()
   endif()
 
+  find_program(OFFLOAD_TBLGEN_EXECUTABLE
+    NAMES offload-tblgen
+    PATHS ${OPENMP_LLVM_TOOLS_DIR})
+  if (NOT OFFLOAD_TBLGEN_EXECUTABLE)
+    message(STATUS "Cannot find 'offload-tblgen'.")
+    message(STATUS "Please put 'not' in your PATH, set OFFLOAD_TBLGEN_EXECUTABLE to its full path, or point OPENMP_LLVM_TOOLS_DIR to its directory.")
+    message(WARNING "The check targets will not be available!")
+    set(ENABLE_CHECK_TARGETS FALSE PARENT_SCOPE)
+    return()
+  endif()
+
   find_program(OPENMP_NOT_EXECUTABLE
     NAMES not
     PATHS ${OPENMP_LLVM_TOOLS_DIR})
@@ -82,6 +93,7 @@ else()
     set(OPENMP_FILECHECK_EXECUTABLE ${LLVM_RUNTIME_OUTPUT_INTDIR}/FileCheck)
   endif()
   set(OPENMP_NOT_EXECUTABLE ${LLVM_RUNTIME_OUTPUT_INTDIR}/not)
+  set(OFFLOAD_TBLGEN_EXECUTABLE ${LLVM_RUNTIME_OUTPUT_INTDIR}/offload-tblgen)
   set(OFFLOAD_DEVICE_INFO_EXECUTABLE ${LLVM_RUNTIME_OUTPUT_INTDIR}/llvm-offload-device-info)
 endif()
 
diff --git a/offload/liboffload/API/APIDefs.td b/offload/liboffload/API/APIDefs.td
new file mode 100644
index 0000000000000..60c1b85d26911
--- /dev/null
+++ b/offload/liboffload/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 = "OL";
+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#"_ERRC_INVALID_NULL_HANDLE")));
+
+  list<Return> returns_out = !if(returns_has_inv_handle,
+        AppendConditionsToReturn<Returns, PREFIX # "_ERRC_INVALID_NULL_HANDLE", handle_param_conds>.ret,
+        !listconcat(Returns, [Return<PREFIX # "_ERRC_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#"_ERRC_INVALID_NULL_POINTER")));
+  list<Return> returns_out = !if(returns_has_inv_ptr,
+        AppendConditionsToReturn<Returns, PREFIX # "_ERRC_INVALID_NULL_POINTER", ptr_param_conds>.ret,
+        !listconcat(Returns, [Return<PREFIX # "_ERRC_INVALID_NULL_POINTER", ptr_param_conds>])
+    );
+}
+
+defvar DefaultReturns = [Return<PREFIX#"_RESULT_SUCCESS">,
+                         Return<PREFIX#"_ERRC_UNINITIALIZED">,
+                         Return<PREFIX#"_ERRC_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/liboffload/API/CMakeLists.txt b/offload/liboffload/API/CMakeLists.txt
new file mode 100644
index 0000000000000..8fd6cb539374a
--- /dev/null
+++ b/offload/liboffload/API/CMakeLists.txt
@@ -0,0 +1,25 @@
+# The OffloadGenerate target is used to regenerate the generated files in the
+# include directory. These files are checked in with the rest of the source,
+# therefore it is only needed when making changes to the API.
+
+find_program(CLANG_FORMAT clang-format PATHS ${LLVM_TOOLS_BINARY_DIR} NO_DEFAULT_PATH)
+if (CLANG_FORMAT)
+    set(LLVM_TARGET_DEFINITIONS ${CMAKE_CURRENT_SOURCE_DIR}/OffloadAPI.td)
+
+    tablegen(OFFLOAD OffloadAPI.h -gen-api)
+    tablegen(OFFLOAD OffloadEntryPoints.inc -gen-entry-points)
+    tablegen(OFFLOAD OffloadFuncs.inc -gen-func-names)
+    tablegen(OFFLOAD OffloadImplFuncDecls.inc -gen-impl-func-decls)
+    tablegen(OFFLOAD OffloadPrint.hpp -gen-print-header)
+
+    set(OFFLOAD_GENERATED_FILES ${TABLEGEN_OUTPUT})
+    add_public_tablegen_target(OffloadGenerate)
+    add_custom_command(TARGET OffloadGenerate POST_BUILD COMMAND ${CLANG_FORMAT}
+        -i ${OFFLOAD_GENERATED_FILES})
+    add_custom_command(TARGET OffloadGenerate POST_BUILD COMMAND ${CMAKE_COMMAND}
+        -E copy_if_different ${OFFLOAD_GENERATED_FILES} "${CMAKE_CURRENT_SOURCE_DIR}/../include/generated")
+else()
+    message(WARNING "clang-format was not found, so the OffloadGenerate target\
+        will not be available. Offload will still build, but you will not be\
+        able to make changes to the API.")
+endif()
diff --git a/offload/liboffload/API/Common.td b/offload/liboffload/API/Common.td
new file mode 100644
index 0000000000000..5b19d1d47129e
--- /dev/null
+++ b/offload/liboffload/API/Common.td
@@ -0,0 +1,141 @@
+def : Macro {
+  let name = "OL_VERSION_MAJOR";
+  let desc = "Major version of the Offload API";
+  let value = "0";
+}
+
+def : Macro {
+  let name = "OL_VERSION_MINOR";
+  let desc = "Minor version of the Offload API";
+  let value = "0";
+}
+
+def : Macro {
+  let name = "OL_VERSION_PATCH";
+  let desc = "Patch version of the Offload API";
+  let value = "1";
+}
+
+def : Macro {
+  let name = "OL_APICALL";
+  let desc = "Calling convention for all API functions";
+  let condition = "defined(_WIN32)";
+  let value = "__cdecl";
+  let alt_value = "";
+}
+
+def : Macro {
+  let name = "OL_APIEXPORT";
+  let desc = "Microsoft-specific dllexport storage-class attribute";
+  let condition = "defined(_WIN32)";
+  let value = "__declspec(dllexport)";
+  let alt_value = "";
+}
+
+def : Macro {
+  let name = "OL_DLLEXPORT";
+  let desc = "Microsoft-specific dllexport storage-class attribute";
+  let condition = "defined(_WIN32)";
+  let value = "__declspec(dllexport)";
+}
+
+def : Macro {
+  let name = "OL_DLLEXPORT";
+  let desc = "GCC-specific dllexport storage-class attribute";
+  let condition = "__GNUC__ >= 4";
+  let value = "__attribute__ ((visibility (\"default\")))";
+  let alt_value = "";
+}
+
+def : Handle {
+  let name = "ol_platform_handle_t";
+  let desc = "Handle of a platform instance";
+}
+
+def : Handle {
+  let name = "ol_device_handle_t";
+  let desc = "Handle of platform's device object";
+}
+
+def : Handle {
+  let name = "ol_context_handle_t";
+  let desc = "Handle of context object";
+}
+
+def : Enum {
+  let name = "ol_errc_t";
+  let desc = "Defines Return/Error codes";
+  let etors =[
+    Etor<"SUCCESS", "Success">,
+    Etor<"INVALID_VALUE", "Invalid Value">,
+    Etor<"INVALID_PLATFORM", "Invalid platform">,
+    Etor<"DEVICE_NOT_FOUND", "Device not found">,
+    Etor<"INVALID_DEVICE", "Invalid device">,
+    Etor<"DEVICE_LOST", "Device hung, reset, was removed, or driver update occurred">,
+    Etor<"UNINITIALIZED", "plugin is not initialized or specific entry-point is not implemented">,
+    Etor<"OUT_OF_RESOURCES", "Out of resources">,
+    Etor<"UNSUPPORTED_VERSION", "generic error code for unsupported versions">,
+    Etor<"UNSUPPORTED_FEATURE", "generic error code for unsupported features">,
+    Etor<"INVALID_ARGUMENT", "generic error code for invalid arguments">,
+    Etor<"INVALID_NULL_HANDLE", "handle argument is not valid">,
+    Etor<"INVALID_NULL_POINTER", "pointer argument may not be nullptr">,
+    Etor<"INVALID_SIZE", "invalid size or dimensions (e.g., must not be zero, or is out of bounds)">,
+    Etor<"INVALID_ENUMERATION", "enumerator argument is not valid">,
+    Etor<"UNSUPPORTED_ENUMERATION", "enumerator argument is not supported by the device">,
+    Etor<"UNKNOWN", "Unknown or internal error">
+  ];
+}
+
+def : Struct {
+  let name = "ol_error_struct_t";
+  let desc = "Details of the error condition returned by an API call";
+  let members = [
+    StructMember<"ol_errc_t", "Code", "The error code">,
+    StructMember<"const char*", "Details", "String containing error details">
+  ];
+}
+
+def : Typedef {
+  let name = "ol_result_t";
+  let desc = "Result type returned by all entry points.";
+  let value = "const ol_error_struct_t*";
+}
+
+def : Macro {
+  let name = "OL_SUCCESS";
+  let desc = "Success condition";
+  let value = "NULL";
+}
+
+def : Struct {
+  let name = "ol_code_location_t";
+  let desc = "Code location information that can optionally be associated with an API call";
+  let members = [
+    StructMember<"const char*", "FunctionName", "Function name">,
+    StructMember<"const char*", "SourceFile", "Source code file">,
+    StructMember<"uint32_t", "LineNumber", "Source code line number">,
+    StructMember<"uint32_t", "ColumnNumber", "Source code column number">
+  ];
+}
+
+def : Function {
+  let name = "olInit";
+  let desc = "Perform initialization of the Offload library and plugins";
+  let details = [
+    "This must be the first API call made by a user of the Offload library",
+    "Each call will increment an internal reference count that is decremented by `olShutDown`"
+  ];
+  let params = [];
+  let returns = [];
+}
+
+def : Function {
+  let name = "olShutDown";
+  let desc = "Release the resources in use by Offload";
+  let details = [
+    "This decrements an internal reference count. When this reaches 0, all resources will be released",
+    "Subsequent API calls made after this are not valid"
+  ];
+  let params = [];
+  let returns = [];
+}
diff --git a/offload/liboffload/API/Device.td b/offload/liboffload/API/Device.td
new file mode 100644
index 0000000000000..30c0b71fe7b37
--- /dev/null
+++ b/offload/liboffload/API/Device.td
@@ -0,0 +1,106 @@
+//===-- 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 = "ol_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 = "ol_device_info_t";
+  let desc = "Supported device info";
+  let is_typed = 1;
+  let etors =[
+    TaggedEtor<"TYPE", "ol_device_type_t", "type of the device">,
+    TaggedEtor<"PLATFORM", "ol_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 = "olGetDeviceCount";
+  let desc = "Retrieves the number of available devices within a platform";
+  let params = [
+    Param<"ol_platform_handle_t", "Platform", "handle of the platform instance", PARAM_IN>,
+    Param<"uint32_t*", "NumDevices", "pointer to the number of devices.", PARAM_OUT>
+  ];
+  let returns = [];
+}
+
+def : Function {
+  let name = "olGetDevice";
+  let desc = "Retrieves devices within a platform";
+  let details = [
+    "Multiple calls to this function will return identical device handles, in the same order.",
+  ];
+  let params = [
+    Param<"ol_platform_handle_t", "Platform", "handle of the platform instance", PARAM_IN>,
+    Param<"uint32_t", "NumEntries", "the number of devices to be added to phDevices, which must be greater than zero", PARAM_IN>,
+    RangedParam<"ol_device_handle_t*", "Devices", "Array of device handles. "
+        "If NumEntries is less than the number of devices available, then this function shall only retrieve that number of devices.", PARAM_OUT,
+        Range<"0", "NumEntries">>
+  ];
+  let returns = [
+    Return<"OL_ERRC_INVALID_SIZE", [
+      "`NumEntries == 0`"
+    ]>
+  ];
+}
+
+def : Function {
+  let name = "olGetDeviceInfo";
+  let desc = "Queries the given property of the device";
+  let details = [];
+  let params = [
+    Param<"ol_device_handle_t", "Device", "handle of the device instance", PARAM_IN>,
+    Param<"ol_device_info_t", "PropName", "type of the info to retrieve", PARAM_IN>,
+    Param<"size_t", "PropSize", "the number of bytes pointed to by PropValue.", PARAM_IN>,
+    TypeTaggedParam<"void*", "PropValue", "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 OL_ERRC_INVALID_SIZE error is returned and "
+                    "PropValue is not used.", PARAM_OUT, TypeInfo<"PropName" , "PropSize">>
+  ];
+  let returns = [
+    Return<"OL_ERRC_UNSUPPORTED_ENUMERATION", [
+      "If `PropName` is not supported by the device."
+    ]>,
+    Return<"OL_ERRC_INVALID_SIZE", [
+      "`PropSize == 0`",
+      "If `PropSize` is less than the real number of bytes needed to return the info."
+    ]>,
+    Return<"OL_ERRC_INVALID_DEVICE">
+  ];
+}
+
+def : Function {
+  let name = "olGetDeviceInfoSize";
+  let desc = "Returns the storage size of the given device query";
+  let details = [];
+  let params = [
+    Param<"ol_device_handle_t", "Device", "handle of the device instance", PARAM_IN>,
+    Param<"ol_device_info_t", "PropName", "type of the info to retrieve", PARAM_IN>,
+    Param<"size_t*", "PropSizeRet", "pointer to the number of bytes required to store the query", PARAM_OUT>
+  ];
+  let returns = [
+    Return<"OL_ERRC_UNSUPPORTED_ENUMERATION", [
+      "If `PropName` is not supported by the device."
+    ]>,
+    Return<"OL_ERRC_INVALID_DEVICE">
+  ];
+}
diff --git a/offload/liboffload/API/OffloadAPI.td b/offload/liboffload/API/OffloadAPI.td
new file mode 100644
index 0000000000000..8a0c3c4058122
--- /dev/null
+++ b/offload/liboffload/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/liboffload/API/Platform.td b/offload/liboffload/API/Platform.td
new file mode 100644
index 0000000000000..03e70cf96ac94
--- /dev/null
+++ b/offload/liboffload/API/Platform.td
@@ -0,0 +1,112 @@
+//===-- 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 = "olGetPlatform";
+  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 Platforms. NumEntries must be "
+      "greater than zero.",
+      PARAM_IN>,
+    RangedParam<"ol_platform_handle_t*", "Platforms", 
+      "Array of handle of platforms. If NumEntries is less than the number of "
+      "platforms available, then olGetPlatform shall only retrieve that "
+      "number of platforms.",
+      PARAM_OUT, Range<"0", "NumEntries">>
+  ];
+  let returns = [
+    Return<"OL_ERRC_INVALID_SIZE", [
+      "`NumEntries == 0`"
+    ]>
+  ];
+}
+
+def : Function {
+  let name = "olGetPlatformCount";
+  let desc = "Retrieves the number of available platforms";
+  let params = [
+    Param<"uint32_t*",
+      "NumPlatforms", "returns the total number of platforms available.",
+      PARAM_OUT>
+  ];
+  let returns = [];
+}
+
+def : Enum {
+  let name = "ol_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", "ol_platform_backend_t", "The native backend of the platform.">
+  ];
+}
+
+def : Enum {
+  let name = "ol_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 = "olGetPlatformInfo";
+  let desc = "Queries the given property of the platform";
+  let details = [
+    "`olGetPlatformInfoSize` can be used to query the storage size "
+    "required for the given query."
+  ];
+  let params = [
+    Param<"ol_platform_handle_t", "Platform", "handle of the platform", PARAM_IN>,
+    Param<"ol_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*", "PropValue", "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 OL_ERRC_INVALID_SIZE error is returned and pPlatformInfo is not used.", PARAM_OUT,
+      TypeInfo<"PropName" , "PropSize">>
+  ];
+  let returns = [
+    Return<"OL_ERRC_UNSUPPORTED_ENUMERATION", [
+      "If `PropName` is not supported by the platform."
+    ]>,
+    Return<"OL_ERRC_INVALID_SIZE", [
+      "`PropSize == 0`",
+      "If `PropSize` is less than the real number of bytes needed to return the info."
+    ]>,
+    Return<"OL_ERRC_INVALID_PLATFORM">
+  ];
+}
+
+def : Function {
+  let name = "olGetPlatformInfoSize";
+  let desc = "Returns the storage size of the given platform query";
+  let details = [];
+  let params = [
+    Param<"ol_platform_handle_t", "Platform", "handle of the platform", PARAM_IN>,
+    Param<"ol_platform_info_t", "PropName", "type of the info to query", PARAM_IN>,
+    Param<"size_t*", "PropSizeRet", "pointer to the number of bytes required to store the query", PARAM_OUT>
+  ];
+  let returns = [
+    Return<"OL_ERRC_UNSUPPORTED_ENUMERATION", [
+      "If `PropName` is not supported by the platform."
+    ]>,
+    Return<"OL_ERRC_INVALID_PLATFORM">
+  ];
+}
diff --git a/offload/liboffload/API/README.md b/offload/liboffload/API/README.md
new file mode 100644
index 0000000000000..38a055811b2d0
--- /dev/null
+++ b/offload/liboffload/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. `olGetDeviceInfo`) 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. `olGetDeviceInfo`).
+
+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`
diff --git a/offload/liboffload/CMakeLists.txt b/offload/liboffload/CMakeLists.txt
new file mode 100644
index 0000000000000..27ba9e93c3675
--- /dev/null
+++ b/offload/liboffload/CMakeLists.txt
@@ -0,0 +1,32 @@
+add_subdirectory(API)
+
+add_llvm_library(LLVMOffload SHARED
+                src/OffloadLib.cpp
+                src/OffloadImpl.cpp)
+
+foreach(plugin IN LISTS LIBOMPTARGET_PLUGINS_TO_BUILD)
+    target_link_libraries(LLVMOffload PRIVATE omptarget.rtl.${plugin})
+endforeach()
+
+if(LIBOMP_HAVE_VERSION_SCRIPT_FLAG)
+    target_link_libraries(LLVMOffload PRIVATE "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/exports")
+endif()
+
+target_include_directories(LLVMOffload PUBLIC
+                            ${CMAKE_CURRENT_BINARY_DIR}/../include
+                            ${CMAKE_CURRENT_SOURCE_DIR}/include
+                            ${CMAKE_CURRENT_SOURCE_DIR}/include/generated
+                            ${CMAKE_CURRENT_SOURCE_DIR}/../include
+                            ${CMAKE_CURRENT_SOURCE_DIR}/../plugins-nextgen/common/include)
+
+target_compile_options(LLVMOffload PRIVATE ${offload_compile_flags})
+target_link_options(LLVMOffload PRIVATE ${offload_link_flags})
+
+set_target_properties(LLVMOffload PROPERTIES
+                      POSITION_INDEPENDENT_CODE ON
+                      INSTALL_RPATH "$ORIGIN"
+                      BUILD_RPATH "$ORIGIN:${CMAKE_CURRENT_BINARY_DIR}/..")
+install(TARGETS LLVMOffload LIBRARY COMPONENT LLVMOffload DESTINATION "${OFFLOAD_INSTALL_LIBDIR}")
+
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/OffloadAPI.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include/offload)
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/OffloadPrint.hpp DESTINATION ${CMAKE_INSTALL_PREFIX}/include/offload)
diff --git a/offload/liboffload/README.md b/offload/liboffload/README.md
new file mode 100644
index 0000000000000..95c9bf54d7bad
--- /dev/null
+++ b/offload/liboffload/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/liboffload/exports b/offload/liboffload/exports
new file mode 100644
index 0000000000000..168341aa7d938
--- /dev/null
+++ b/offload/liboffload/exports
@@ -0,0 +1,6 @@
+VERS1.0 {
+global:
+  ol*;
+local:
+  *;
+};
diff --git a/offload/liboffload/include/OffloadImpl.hpp b/offload/liboffload/include/OffloadImpl.hpp
new file mode 100644
index 0000000000000..6d745095f3105
--- /dev/null
+++ b/offload/liboffload/include/OffloadImpl.hpp
@@ -0,0 +1,94 @@
+//===- offload_impl.hpp- Implementation helpers for the Offload library ---===//
+//
+// 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 <OffloadAPI.h>
+#include <iostream>
+#include <memory>
+#include <optional>
+#include <set>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSet.h"
+
+struct OffloadConfig {
+  bool TracingEnabled = false;
+};
+
+OffloadConfig &offloadConfig();
+
+// Use the StringSet container to efficiently deduplicate repeated error
+// strings (e.g. if the same error is hit constantly in a long running program)
+llvm::StringSet<> &errorStrs();
+
+// Use an unordered_set to avoid duplicates of error structs themselves.
+// We cannot store the structs directly as returned pointers to them must always
+// be valid, and a rehash of the set may invalidate them. This requires
+// custom hash and equal_to function objects.
+using ErrPtrT = std::unique_ptr<ol_error_struct_t>;
+struct ErrPtrEqual {
+  bool operator()(const ErrPtrT &lhs, const ErrPtrT &rhs) const {
+    if (!lhs && !rhs) {
+      return true;
+    }
+    if (!lhs || !rhs) {
+      return false;
+    }
+
+    bool StrsEqual = false;
+    if (lhs->Details == NULL && rhs->Details == NULL) {
+      StrsEqual = true;
+    } else if (lhs->Details != NULL && rhs->Details != NULL) {
+      StrsEqual = (std::strcmp(lhs->Details, rhs->Details) == 0);
+    }
+    return (lhs->Code == rhs->Code) && StrsEqual;
+  }
+};
+struct ErrPtrHash {
+  size_t operator()(const ErrPtrT &e) const {
+    if (!e) {
+      // We shouldn't store empty errors (i.e. success), but just in case
+      return 0lu;
+    } else {
+      return std::hash<int>{}(e->Code);
+    }
+  }
+};
+using ErrSetT = std::unordered_set<ErrPtrT, ErrPtrHash, ErrPtrEqual>;
+ErrSetT &errors();
+
+struct ol_impl_result_t {
+  ol_impl_result_t(std::nullptr_t) : Result(OL_SUCCESS) {}
+  ol_impl_result_t(ol_errc_t Code) {
+    if (Code == OL_ERRC_SUCCESS) {
+      Result = nullptr;
+    } else {
+      auto Err = std::unique_ptr<ol_error_struct_t>(
+          new ol_error_struct_t{Code, nullptr});
+      Result = errors().emplace(std::move(Err)).first->get();
+    }
+  }
+
+  ol_impl_result_t(ol_errc_t Code, llvm::StringRef Details) {
+    assert(Code != OL_ERRC_SUCCESS);
+    Result = nullptr;
+    auto DetailsStr = errorStrs().insert(Details).first->getKeyData();
+    auto Err = std::unique_ptr<ol_error_struct_t>(
+        new ol_error_struct_t{Code, DetailsStr});
+    Result = errors().emplace(std::move(Err)).first->get();
+  }
+
+  operator ol_result_t() { return Result; }
+
+private:
+  ol_result_t Result;
+};
diff --git a/offload/liboffload/include/generated/OffloadAPI.h b/offload/liboffload/include/generated/OffloadAPI.h
new file mode 100644
index 0000000000000..11fcc96625ab8
--- /dev/null
+++ b/offload/liboffload/include/generated/OffloadAPI.h
@@ -0,0 +1,610 @@
+//===- Auto-generated file, part of the LLVM/Offload project --------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Auto-generated file, do not manually edit.
+
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+#ifndef OL_VERSION_MAJOR
+/// @brief Major version of the Offload API
+#define OL_VERSION_MAJOR 0
+#endif // OL_VERSION_MAJOR
+
+///////////////////////////////////////////////////////////////////////////////
+#ifndef OL_VERSION_MINOR
+/// @brief Minor version of the Offload API
+#define OL_VERSION_MINOR 0
+#endif // OL_VERSION_MINOR
+
+///////////////////////////////////////////////////////////////////////////////
+#ifndef OL_VERSION_PATCH
+/// @brief Patch version of the Offload API
+#define OL_VERSION_PATCH 1
+#endif // OL_VERSION_PATCH
+
+///////////////////////////////////////////////////////////////////////////////
+#ifndef OL_APICALL
+#if defined(_WIN32)
+/// @brief Calling convention for all API functions
+#define OL_APICALL __cdecl
+#else
+#define OL_APICALL
+#endif // defined(_WIN32)
+#endif // OL_APICALL
+
+///////////////////////////////////////////////////////////////////////////////
+#ifndef OL_APIEXPORT
+#if defined(_WIN32)
+/// @brief Microsoft-specific dllexport storage-class attribute
+#define OL_APIEXPORT __declspec(dllexport)
+#else
+#define OL_APIEXPORT
+#endif // defined(_WIN32)
+#endif // OL_APIEXPORT
+
+///////////////////////////////////////////////////////////////////////////////
+#ifndef OL_DLLEXPORT
+#if defined(_WIN32)
+/// @brief Microsoft-specific dllexport storage-class attribute
+#define OL_DLLEXPORT __declspec(dllexport)
+#endif // defined(_WIN32)
+#endif // OL_DLLEXPORT
+
+///////////////////////////////////////////////////////////////////////////////
+#ifndef OL_DLLEXPORT
+#if __GNUC__ >= 4
+/// @brief GCC-specific dllexport storage-class attribute
+#define OL_DLLEXPORT __attribute__((visibility("default")))
+#else
+#define OL_DLLEXPORT
+#endif // __GNUC__ >= 4
+#endif // OL_DLLEXPORT
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Handle of a platform instance
+typedef struct ol_platform_handle_t_ *ol_platform_handle_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Handle of platform's device object
+typedef struct ol_device_handle_t_ *ol_device_handle_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Handle of context object
+typedef struct ol_context_handle_t_ *ol_context_handle_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Defines Return/Error codes
+typedef enum ol_errc_t {
+  /// Success
+  OL_ERRC_SUCCESS = 0,
+  /// Invalid Value
+  OL_ERRC_INVALID_VALUE = 1,
+  /// Invalid platform
+  OL_ERRC_INVALID_PLATFORM = 2,
+  /// Device not found
+  OL_ERRC_DEVICE_NOT_FOUND = 3,
+  /// Invalid device
+  OL_ERRC_INVALID_DEVICE = 4,
+  /// Device hung, reset, was removed, or driver update occurred
+  OL_ERRC_DEVICE_LOST = 5,
+  /// plugin is not initialized or specific entry-point is not implemented
+  OL_ERRC_UNINITIALIZED = 6,
+  /// Out of resources
+  OL_ERRC_OUT_OF_RESOURCES = 7,
+  /// generic error code for unsupported versions
+  OL_ERRC_UNSUPPORTED_VERSION = 8,
+  /// generic error code for unsupported features
+  OL_ERRC_UNSUPPORTED_FEATURE = 9,
+  /// generic error code for invalid arguments
+  OL_ERRC_INVALID_ARGUMENT = 10,
+  /// handle argument is not valid
+  OL_ERRC_INVALID_NULL_HANDLE = 11,
+  /// pointer argument may not be nullptr
+  OL_ERRC_INVALID_NULL_POINTER = 12,
+  /// invalid size or dimensions (e.g., must not be zero, or is out of bounds)
+  OL_ERRC_INVALID_SIZE = 13,
+  /// enumerator argument is not valid
+  OL_ERRC_INVALID_ENUMERATION = 14,
+  /// enumerator argument is not supported by the device
+  OL_ERRC_UNSUPPORTED_ENUMERATION = 15,
+  /// Unknown or internal error
+  OL_ERRC_UNKNOWN = 16,
+  /// @cond
+  OL_ERRC_FORCE_UINT32 = 0x7fffffff
+  /// @endcond
+
+} ol_errc_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Details of the error condition returned by an API call
+typedef struct ol_error_struct_t {
+  ol_errc_t Code;      /// The error code
+  const char *Details; /// String containing error details
+} ol_error_struct_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Result type returned by all entry points.
+typedef const ol_error_struct_t *ol_result_t;
+
+///////////////////////////////////////////////////////////////////////////////
+#ifndef OL_SUCCESS
+/// @brief Success condition
+#define OL_SUCCESS NULL
+#endif // OL_SUCCESS
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Code location information that can optionally be associated with an
+/// API call
+typedef struct ol_code_location_t {
+  const char *FunctionName; /// Function name
+  const char *SourceFile;   /// Source code file
+  uint32_t LineNumber;      /// Source code line number
+  uint32_t ColumnNumber;    /// Source code column number
+} ol_code_location_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Perform initialization of the Offload library and plugins
+///
+/// @details
+///    - This must be the first API call made by a user of the Offload library
+///    - Each call will increment an internal reference count that is
+///    decremented by `olShutDown`
+///
+/// @returns
+///     - ::OL_RESULT_SUCCESS
+///     - ::OL_ERRC_UNINITIALIZED
+///     - ::OL_ERRC_DEVICE_LOST
+///     - ::OL_ERRC_INVALID_NULL_HANDLE
+///     - ::OL_ERRC_INVALID_NULL_POINTER
+OL_APIEXPORT ol_result_t OL_APICALL olInit();
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Release the resources in use by Offload
+///
+/// @details
+///    - This decrements an internal reference count. When this reaches 0, all
+///    resources will be released
+///    - Subsequent API calls made after this are not valid
+///
+/// @returns
+///     - ::OL_RESULT_SUCCESS
+///     - ::OL_ERRC_UNINITIALIZED
+///     - ::OL_ERRC_DEVICE_LOST
+///     - ::OL_ERRC_INVALID_NULL_HANDLE
+///     - ::OL_ERRC_INVALID_NULL_POINTER
+OL_APIEXPORT ol_result_t OL_APICALL olShutDown();
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Retrieves all available platforms
+///
+/// @details
+///    - Multiple calls to this function will return identical platforms
+///    handles, in the same order.
+///
+/// @returns
+///     - ::OL_RESULT_SUCCESS
+///     - ::OL_ERRC_UNINITIALIZED
+///     - ::OL_ERRC_DEVICE_LOST
+///     - ::OL_ERRC_INVALID_SIZE
+///         + `NumEntries == 0`
+///     - ::OL_ERRC_INVALID_NULL_HANDLE
+///     - ::OL_ERRC_INVALID_NULL_POINTER
+///         + `NULL == Platforms`
+OL_APIEXPORT ol_result_t OL_APICALL olGetPlatform(
+    // [in] The number of platforms to be added to Platforms. NumEntries must be
+    // greater than zero.
+    uint32_t NumEntries,
+    // [out] Array of handle of platforms. If NumEntries is less than the number
+    // of platforms available, then olGetPlatform shall only retrieve that
+    // number of platforms.
+    ol_platform_handle_t *Platforms);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Retrieves the number of available platforms
+///
+/// @details
+///
+/// @returns
+///     - ::OL_RESULT_SUCCESS
+///     - ::OL_ERRC_UNINITIALIZED
+///     - ::OL_ERRC_DEVICE_LOST
+///     - ::OL_ERRC_INVALID_NULL_HANDLE
+///     - ::OL_ERRC_INVALID_NULL_POINTER
+///         + `NULL == NumPlatforms`
+OL_APIEXPORT ol_result_t OL_APICALL olGetPlatformCount(
+    // [out] returns the total number of platforms available.
+    uint32_t *NumPlatforms);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Supported platform info
+typedef enum ol_platform_info_t {
+  /// [char[]] The string denoting name of the platform. The size of the info
+  /// needs to be dynamically queried.
+  OL_PLATFORM_INFO_NAME = 0,
+  /// [char[]] The string denoting name of the vendor of the platform. The size
+  /// of the info needs to be dynamically queried.
+  OL_PLATFORM_INFO_VENDOR_NAME = 1,
+  /// [char[]] The string denoting the version of the platform. The size of the
+  /// info needs to be dynamically queried.
+  OL_PLATFORM_INFO_VERSION = 2,
+  /// [ol_platform_backend_t] The native backend of the platform.
+  OL_PLATFORM_INFO_BACKEND = 3,
+  /// @cond
+  OL_PLATFORM_INFO_FORCE_UINT32 = 0x7fffffff
+  /// @endcond
+
+} ol_platform_info_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Identifies the native backend of the platform
+typedef enum ol_platform_backend_t {
+  /// The backend is not recognized
+  OL_PLATFORM_BACKEND_UNKNOWN = 0,
+  /// The backend is CUDA
+  OL_PLATFORM_BACKEND_CUDA = 1,
+  /// The backend is AMDGPU
+  OL_PLATFORM_BACKEND_AMDGPU = 2,
+  /// @cond
+  OL_PLATFORM_BACKEND_FORCE_UINT32 = 0x7fffffff
+  /// @endcond
+
+} ol_platform_backend_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Queries the given property of the platform
+///
+/// @details
+///    - `olGetPlatformInfoSize` can be used to query the storage size required
+///    for the given query.
+///
+/// @returns
+///     - ::OL_RESULT_SUCCESS
+///     - ::OL_ERRC_UNINITIALIZED
+///     - ::OL_ERRC_DEVICE_LOST
+///     - ::OL_ERRC_UNSUPPORTED_ENUMERATION
+///         + If `PropName` is not supported by the platform.
+///     - ::OL_ERRC_INVALID_SIZE
+///         + `PropSize == 0`
+///         + If `PropSize` is less than the real number of bytes needed to
+///         return the info.
+///     - ::OL_ERRC_INVALID_PLATFORM
+///     - ::OL_ERRC_INVALID_NULL_HANDLE
+///         + `NULL == Platform`
+///     - ::OL_ERRC_INVALID_NULL_POINTER
+///         + `NULL == PropValue`
+OL_APIEXPORT ol_result_t OL_APICALL olGetPlatformInfo(
+    // [in] handle of the platform
+    ol_platform_handle_t Platform,
+    // [in] type of the info to retrieve
+    ol_platform_info_t PropName,
+    // [in] the number of bytes pointed to by pPlatformInfo.
+    size_t PropSize,
+    // [out] 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
+    // OL_ERRC_INVALID_SIZE error is returned and pPlatformInfo is not used.
+    void *PropValue);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Returns the storage size of the given platform query
+///
+/// @details
+///
+/// @returns
+///     - ::OL_RESULT_SUCCESS
+///     - ::OL_ERRC_UNINITIALIZED
+///     - ::OL_ERRC_DEVICE_LOST
+///     - ::OL_ERRC_UNSUPPORTED_ENUMERATION
+///         + If `PropName` is not supported by the platform.
+///     - ::OL_ERRC_INVALID_PLATFORM
+///     - ::OL_ERRC_INVALID_NULL_HANDLE
+///         + `NULL == Platform`
+///     - ::OL_ERRC_INVALID_NULL_POINTER
+///         + `NULL == PropSizeRet`
+OL_APIEXPORT ol_result_t OL_APICALL olGetPlatformInfoSize(
+    // [in] handle of the platform
+    ol_platform_handle_t Platform,
+    // [in] type of the info to query
+    ol_platform_info_t PropName,
+    // [out] pointer to the number of bytes required to store the query
+    size_t *PropSizeRet);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Supported device types
+typedef enum ol_device_type_t {
+  /// The default device type as preferred by the runtime
+  OL_DEVICE_TYPE_DEFAULT = 0,
+  /// Devices of all types
+  OL_DEVICE_TYPE_ALL = 1,
+  /// GPU device type
+  OL_DEVICE_TYPE_GPU = 2,
+  /// CPU device type
+  OL_DEVICE_TYPE_CPU = 3,
+  /// @cond
+  OL_DEVICE_TYPE_FORCE_UINT32 = 0x7fffffff
+  /// @endcond
+
+} ol_device_type_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Supported device info
+typedef enum ol_device_info_t {
+  /// [ol_device_type_t] type of the device
+  OL_DEVICE_INFO_TYPE = 0,
+  /// [ol_platform_handle_t] the platform associated with the device
+  OL_DEVICE_INFO_PLATFORM = 1,
+  /// [char[]] Device name
+  OL_DEVICE_INFO_NAME = 2,
+  /// [char[]] Device vendor
+  OL_DEVICE_INFO_VENDOR = 3,
+  /// [char[]] Driver version
+  OL_DEVICE_INFO_DRIVER_VERSION = 4,
+  /// @cond
+  OL_DEVICE_INFO_FORCE_UINT32 = 0x7fffffff
+  /// @endcond
+
+} ol_device_info_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Retrieves the number of available devices within a platform
+///
+/// @details
+///
+/// @returns
+///     - ::OL_RESULT_SUCCESS
+///     - ::OL_ERRC_UNINITIALIZED
+///     - ::OL_ERRC_DEVICE_LOST
+///     - ::OL_ERRC_INVALID_NULL_HANDLE
+///         + `NULL == Platform`
+///     - ::OL_ERRC_INVALID_NULL_POINTER
+///         + `NULL == NumDevices`
+OL_APIEXPORT ol_result_t OL_APICALL olGetDeviceCount(
+    // [in] handle of the platform instance
+    ol_platform_handle_t Platform,
+    // [out] pointer to the number of devices.
+    uint32_t *NumDevices);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Retrieves devices within a platform
+///
+/// @details
+///    - Multiple calls to this function will return identical device handles,
+///    in the same order.
+///
+/// @returns
+///     - ::OL_RESULT_SUCCESS
+///     - ::OL_ERRC_UNINITIALIZED
+///     - ::OL_ERRC_DEVICE_LOST
+///     - ::OL_ERRC_INVALID_SIZE
+///         + `NumEntries == 0`
+///     - ::OL_ERRC_INVALID_NULL_HANDLE
+///         + `NULL == Platform`
+///     - ::OL_ERRC_INVALID_NULL_POINTER
+///         + `NULL == Devices`
+OL_APIEXPORT ol_result_t OL_APICALL olGetDevice(
+    // [in] handle of the platform instance
+    ol_platform_handle_t Platform,
+    // [in] the number of devices to be added to phDevices, which must be
+    // greater than zero
+    uint32_t NumEntries,
+    // [out] Array of device handles. If NumEntries is less than the number of
+    // devices available, then this function shall only retrieve that number of
+    // devices.
+    ol_device_handle_t *Devices);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Queries the given property of the device
+///
+/// @details
+///
+/// @returns
+///     - ::OL_RESULT_SUCCESS
+///     - ::OL_ERRC_UNINITIALIZED
+///     - ::OL_ERRC_DEVICE_LOST
+///     - ::OL_ERRC_UNSUPPORTED_ENUMERATION
+///         + If `PropName` is not supported by the device.
+///     - ::OL_ERRC_INVALID_SIZE
+///         + `PropSize == 0`
+///         + If `PropSize` is less than the real number of bytes needed to
+///         return the info.
+///     - ::OL_ERRC_INVALID_DEVICE
+///     - ::OL_ERRC_INVALID_NULL_HANDLE
+///         + `NULL == Device`
+///     - ::OL_ERRC_INVALID_NULL_POINTER
+///         + `NULL == PropValue`
+OL_APIEXPORT ol_result_t OL_APICALL olGetDeviceInfo(
+    // [in] handle of the device instance
+    ol_device_handle_t Device,
+    // [in] type of the info to retrieve
+    ol_device_info_t PropName,
+    // [in] the number of bytes pointed to by PropValue.
+    size_t PropSize,
+    // [out] 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
+    // OL_ERRC_INVALID_SIZE error is returned and PropValue is not used.
+    void *PropValue);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Returns the storage size of the given device query
+///
+/// @details
+///
+/// @returns
+///     - ::OL_RESULT_SUCCESS
+///     - ::OL_ERRC_UNINITIALIZED
+///     - ::OL_ERRC_DEVICE_LOST
+///     - ::OL_ERRC_UNSUPPORTED_ENUMERATION
+///         + If `PropName` is not supported by the device.
+///     - ::OL_ERRC_INVALID_DEVICE
+///     - ::OL_ERRC_INVALID_NULL_HANDLE
+///         + `NULL == Device`
+///     - ::OL_ERRC_INVALID_NULL_POINTER
+///         + `NULL == PropSizeRet`
+OL_APIEXPORT ol_result_t OL_APICALL olGetDeviceInfoSize(
+    // [in] handle of the device instance
+    ol_device_handle_t Device,
+    // [in] type of the info to retrieve
+    ol_device_info_t PropName,
+    // [out] pointer to the number of bytes required to store the query
+    size_t *PropSizeRet);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Function parameters for olGetPlatform
+/// @details Each entry is a pointer to the parameter passed to the function;
+typedef struct ol_get_platform_params_t {
+  uint32_t *pNumEntries;
+  ol_platform_handle_t **pPlatforms;
+} ol_get_platform_params_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Function parameters for olGetPlatformCount
+/// @details Each entry is a pointer to the parameter passed to the function;
+typedef struct ol_get_platform_count_params_t {
+  uint32_t **pNumPlatforms;
+} ol_get_platform_count_params_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Function parameters for olGetPlatformInfo
+/// @details Each entry is a pointer to the parameter passed to the function;
+typedef struct ol_get_platform_info_params_t {
+  ol_platform_handle_t *pPlatform;
+  ol_platform_info_t *pPropName;
+  size_t *pPropSize;
+  void **pPropValue;
+} ol_get_platform_info_params_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Function parameters for olGetPlatformInfoSize
+/// @details Each entry is a pointer to the parameter passed to the function;
+typedef struct ol_get_platform_info_size_params_t {
+  ol_platform_handle_t *pPlatform;
+  ol_platform_info_t *pPropName;
+  size_t **pPropSizeRet;
+} ol_get_platform_info_size_params_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Function parameters for olGetDeviceCount
+/// @details Each entry is a pointer to the parameter passed to the function;
+typedef struct ol_get_device_count_params_t {
+  ol_platform_handle_t *pPlatform;
+  uint32_t **pNumDevices;
+} ol_get_device_count_params_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Function parameters for olGetDevice
+/// @details Each entry is a pointer to the parameter passed to the function;
+typedef struct ol_get_device_params_t {
+  ol_platform_handle_t *pPlatform;
+  uint32_t *pNumEntries;
+  ol_device_handle_t **pDevices;
+} ol_get_device_params_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Function parameters for olGetDeviceInfo
+/// @details Each entry is a pointer to the parameter passed to the function;
+typedef struct ol_get_device_info_params_t {
+  ol_device_handle_t *pDevice;
+  ol_device_info_t *pPropName;
+  size_t *pPropSize;
+  void **pPropValue;
+} ol_get_device_info_params_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Function parameters for olGetDeviceInfoSize
+/// @details Each entry is a pointer to the parameter passed to the function;
+typedef struct ol_get_device_info_size_params_t {
+  ol_device_handle_t *pDevice;
+  ol_device_info_t *pPropName;
+  size_t **pPropSizeRet;
+} ol_get_device_info_size_params_t;
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Variant of olInit that also sets source code location information
+/// @details See also ::olInit
+OL_APIEXPORT ol_result_t OL_APICALL
+olInitWithCodeLoc(ol_code_location_t *CodeLocation);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Variant of olShutDown that also sets source code location information
+/// @details See also ::olShutDown
+OL_APIEXPORT ol_result_t OL_APICALL
+olShutDownWithCodeLoc(ol_code_location_t *CodeLocation);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Variant of olGetPlatform that also sets source code location
+/// information
+/// @details See also ::olGetPlatform
+OL_APIEXPORT ol_result_t OL_APICALL
+olGetPlatformWithCodeLoc(uint32_t NumEntries, ol_platform_handle_t *Platforms,
+                         ol_code_location_t *CodeLocation);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Variant of olGetPlatformCount that also sets source code location
+/// information
+/// @details See also ::olGetPlatformCount
+OL_APIEXPORT ol_result_t OL_APICALL olGetPlatformCountWithCodeLoc(
+    uint32_t *NumPlatforms, ol_code_location_t *CodeLocation);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Variant of olGetPlatformInfo that also sets source code location
+/// information
+/// @details See also ::olGetPlatformInfo
+OL_APIEXPORT ol_result_t OL_APICALL olGetPlatformInfoWithCodeLoc(
+    ol_platform_handle_t Platform, ol_platform_info_t PropName, size_t PropSize,
+    void *PropValue, ol_code_location_t *CodeLocation);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Variant of olGetPlatformInfoSize that also sets source code location
+/// information
+/// @details See also ::olGetPlatformInfoSize
+OL_APIEXPORT ol_result_t OL_APICALL olGetPlatformInfoSizeWithCodeLoc(
+    ol_platform_handle_t Platform, ol_platform_info_t PropName,
+    size_t *PropSizeRet, ol_code_location_t *CodeLocation);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Variant of olGetDeviceCount that also sets source code location
+/// information
+/// @details See also ::olGetDeviceCount
+OL_APIEXPORT ol_result_t OL_APICALL
+olGetDeviceCountWithCodeLoc(ol_platform_handle_t Platform, uint32_t *NumDevices,
+                            ol_code_location_t *CodeLocation);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Variant of olGetDevice that also sets source code location
+/// information
+/// @details See also ::olGetDevice
+OL_APIEXPORT ol_result_t OL_APICALL olGetDeviceWithCodeLoc(
+    ol_platform_handle_t Platform, uint32_t NumEntries,
+    ol_device_handle_t *Devices, ol_code_location_t *CodeLocation);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Variant of olGetDeviceInfo that also sets source code location
+/// information
+/// @details See also ::olGetDeviceInfo
+OL_APIEXPORT ol_result_t OL_APICALL olGetDeviceInfoWithCodeLoc(
+    ol_device_handle_t Device, ol_device_info_t PropName, size_t PropSize,
+    void *PropValue, ol_code_location_t *CodeLocation);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Variant of olGetDeviceInfoSize that also sets source code location
+/// information
+/// @details See also ::olGetDeviceInfoSize
+OL_APIEXPORT ol_result_t OL_APICALL olGetDeviceInfoSizeWithCodeLoc(
+    ol_device_handle_t Device, ol_device_info_t PropName, size_t *PropSizeRet,
+    ol_code_location_t *CodeLocation);
+
+#if defined(__cplusplus)
+} // extern "C"
+#endif
diff --git a/offload/liboffload/include/generated/OffloadEntryPoints.inc b/offload/liboffload/include/generated/OffloadEntryPoints.inc
new file mode 100644
index 0000000000000..49c1c8169615e
--- /dev/null
+++ b/offload/liboffload/include/generated/OffloadEntryPoints.inc
@@ -0,0 +1,441 @@
+//===- Auto-generated file, part of the LLVM/Offload project --------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+///////////////////////////////////////////////////////////////////////////////
+ol_impl_result_t olInit_val() {
+  if (true /*enableParameterValidation*/) {
+  }
+
+  return olInit_impl();
+}
+OL_APIEXPORT ol_result_t OL_APICALL olInit() {
+  if (offloadConfig().TracingEnabled) {
+    std::cout << "---> olInit";
+  }
+
+  ol_result_t Result = olInit_val();
+
+  if (offloadConfig().TracingEnabled) {
+    std::cout << "()";
+    std::cout << "-> " << Result << "\n";
+    if (Result && Result->Details) {
+      std::cout << "     *Error Details* " << Result->Details << " \n";
+    }
+  }
+  return Result;
+}
+ol_result_t olInitWithCodeLoc(ol_code_location_t *CodeLocation) {
+  currentCodeLocation() = CodeLocation;
+  ol_result_t Result = olInit();
+
+  currentCodeLocation() = nullptr;
+  return Result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+ol_impl_result_t olShutDown_val() {
+  if (true /*enableParameterValidation*/) {
+  }
+
+  return olShutDown_impl();
+}
+OL_APIEXPORT ol_result_t OL_APICALL olShutDown() {
+  if (offloadConfig().TracingEnabled) {
+    std::cout << "---> olShutDown";
+  }
+
+  ol_result_t Result = olShutDown_val();
+
+  if (offloadConfig().TracingEnabled) {
+    std::cout << "()";
+    std::cout << "-> " << Result << "\n";
+    if (Result && Result->Details) {
+      std::cout << "     *Error Details* " << Result->Details << " \n";
+    }
+  }
+  return Result;
+}
+ol_result_t olShutDownWithCodeLoc(ol_code_location_t *CodeLocation) {
+  currentCodeLocation() = CodeLocation;
+  ol_result_t Result = olShutDown();
+
+  currentCodeLocation() = nullptr;
+  return Result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+ol_impl_result_t olGetPlatform_val(uint32_t NumEntries,
+                                   ol_platform_handle_t *Platforms) {
+  if (true /*enableParameterValidation*/) {
+    if (NumEntries == 0) {
+      return OL_ERRC_INVALID_SIZE;
+    }
+
+    if (NULL == Platforms) {
+      return OL_ERRC_INVALID_NULL_POINTER;
+    }
+  }
+
+  return olGetPlatform_impl(NumEntries, Platforms);
+}
+OL_APIEXPORT ol_result_t OL_APICALL
+olGetPlatform(uint32_t NumEntries, ol_platform_handle_t *Platforms) {
+  if (offloadConfig().TracingEnabled) {
+    std::cout << "---> olGetPlatform";
+  }
+
+  ol_result_t Result = olGetPlatform_val(NumEntries, Platforms);
+
+  if (offloadConfig().TracingEnabled) {
+    ol_get_platform_params_t Params = {&NumEntries, &Platforms};
+    std::cout << "(" << &Params << ")";
+    std::cout << "-> " << Result << "\n";
+    if (Result && Result->Details) {
+      std::cout << "     *Error Details* " << Result->Details << " \n";
+    }
+  }
+  return Result;
+}
+ol_result_t olGetPlatformWithCodeLoc(uint32_t NumEntries,
+                                     ol_platform_handle_t *Platforms,
+                                     ol_code_location_t *CodeLocation) {
+  currentCodeLocation() = CodeLocation;
+  ol_result_t Result = olGetPlatform(NumEntries, Platforms);
+
+  currentCodeLocation() = nullptr;
+  return Result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+ol_impl_result_t olGetPlatformCount_val(uint32_t *NumPlatforms) {
+  if (true /*enableParameterValidation*/) {
+    if (NULL == NumPlatforms) {
+      return OL_ERRC_INVALID_NULL_POINTER;
+    }
+  }
+
+  return olGetPlatformCount_impl(NumPlatforms);
+}
+OL_APIEXPORT ol_result_t OL_APICALL olGetPlatformCount(uint32_t *NumPlatforms) {
+  if (offloadConfig().TracingEnabled) {
+    std::cout << "---> olGetPlatformCount";
+  }
+
+  ol_result_t Result = olGetPlatformCount_val(NumPlatforms);
+
+  if (offloadConfig().TracingEnabled) {
+    ol_get_platform_count_params_t Params = {&NumPlatforms};
+    std::cout << "(" << &Params << ")";
+    std::cout << "-> " << Result << "\n";
+    if (Result && Result->Details) {
+      std::cout << "     *Error Details* " << Result->Details << " \n";
+    }
+  }
+  return Result;
+}
+ol_result_t olGetPlatformCountWithCodeLoc(uint32_t *NumPlatforms,
+                                          ol_code_location_t *CodeLocation) {
+  currentCodeLocation() = CodeLocation;
+  ol_result_t Result = olGetPlatformCount(NumPlatforms);
+
+  currentCodeLocation() = nullptr;
+  return Result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+ol_impl_result_t olGetPlatformInfo_val(ol_platform_handle_t Platform,
+                                       ol_platform_info_t PropName,
+                                       size_t PropSize, void *PropValue) {
+  if (true /*enableParameterValidation*/) {
+    if (PropSize == 0) {
+      return OL_ERRC_INVALID_SIZE;
+    }
+
+    if (NULL == Platform) {
+      return OL_ERRC_INVALID_NULL_HANDLE;
+    }
+
+    if (NULL == PropValue) {
+      return OL_ERRC_INVALID_NULL_POINTER;
+    }
+  }
+
+  return olGetPlatformInfo_impl(Platform, PropName, PropSize, PropValue);
+}
+OL_APIEXPORT ol_result_t OL_APICALL
+olGetPlatformInfo(ol_platform_handle_t Platform, ol_platform_info_t PropName,
+                  size_t PropSize, void *PropValue) {
+  if (offloadConfig().TracingEnabled) {
+    std::cout << "---> olGetPlatformInfo";
+  }
+
+  ol_result_t Result =
+      olGetPlatformInfo_val(Platform, PropName, PropSize, PropValue);
+
+  if (offloadConfig().TracingEnabled) {
+    ol_get_platform_info_params_t Params = {&Platform, &PropName, &PropSize,
+                                            &PropValue};
+    std::cout << "(" << &Params << ")";
+    std::cout << "-> " << Result << "\n";
+    if (Result && Result->Details) {
+      std::cout << "     *Error Details* " << Result->Details << " \n";
+    }
+  }
+  return Result;
+}
+ol_result_t olGetPlatformInfoWithCodeLoc(ol_platform_handle_t Platform,
+                                         ol_platform_info_t PropName,
+                                         size_t PropSize, void *PropValue,
+                                         ol_code_location_t *CodeLocation) {
+  currentCodeLocation() = CodeLocation;
+  ol_result_t Result =
+      olGetPlatformInfo(Platform, PropName, PropSize, PropValue);
+
+  currentCodeLocation() = nullptr;
+  return Result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+ol_impl_result_t olGetPlatformInfoSize_val(ol_platform_handle_t Platform,
+                                           ol_platform_info_t PropName,
+                                           size_t *PropSizeRet) {
+  if (true /*enableParameterValidation*/) {
+    if (NULL == Platform) {
+      return OL_ERRC_INVALID_NULL_HANDLE;
+    }
+
+    if (NULL == PropSizeRet) {
+      return OL_ERRC_INVALID_NULL_POINTER;
+    }
+  }
+
+  return olGetPlatformInfoSize_impl(Platform, PropName, PropSizeRet);
+}
+OL_APIEXPORT ol_result_t OL_APICALL
+olGetPlatformInfoSize(ol_platform_handle_t Platform,
+                      ol_platform_info_t PropName, size_t *PropSizeRet) {
+  if (offloadConfig().TracingEnabled) {
+    std::cout << "---> olGetPlatformInfoSize";
+  }
+
+  ol_result_t Result =
+      olGetPlatformInfoSize_val(Platform, PropName, PropSizeRet);
+
+  if (offloadConfig().TracingEnabled) {
+    ol_get_platform_info_size_params_t Params = {&Platform, &PropName,
+                                                 &PropSizeRet};
+    std::cout << "(" << &Params << ")";
+    std::cout << "-> " << Result << "\n";
+    if (Result && Result->Details) {
+      std::cout << "     *Error Details* " << Result->Details << " \n";
+    }
+  }
+  return Result;
+}
+ol_result_t olGetPlatformInfoSizeWithCodeLoc(ol_platform_handle_t Platform,
+                                             ol_platform_info_t PropName,
+                                             size_t *PropSizeRet,
+                                             ol_code_location_t *CodeLocation) {
+  currentCodeLocation() = CodeLocation;
+  ol_result_t Result = olGetPlatformInfoSize(Platform, PropName, PropSizeRet);
+
+  currentCodeLocation() = nullptr;
+  return Result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+ol_impl_result_t olGetDeviceCount_val(ol_platform_handle_t Platform,
+                                      uint32_t *NumDevices) {
+  if (true /*enableParameterValidation*/) {
+    if (NULL == Platform) {
+      return OL_ERRC_INVALID_NULL_HANDLE;
+    }
+
+    if (NULL == NumDevices) {
+      return OL_ERRC_INVALID_NULL_POINTER;
+    }
+  }
+
+  return olGetDeviceCount_impl(Platform, NumDevices);
+}
+OL_APIEXPORT ol_result_t OL_APICALL
+olGetDeviceCount(ol_platform_handle_t Platform, uint32_t *NumDevices) {
+  if (offloadConfig().TracingEnabled) {
+    std::cout << "---> olGetDeviceCount";
+  }
+
+  ol_result_t Result = olGetDeviceCount_val(Platform, NumDevices);
+
+  if (offloadConfig().TracingEnabled) {
+    ol_get_device_count_params_t Params = {&Platform, &NumDevices};
+    std::cout << "(" << &Params << ")";
+    std::cout << "-> " << Result << "\n";
+    if (Result && Result->Details) {
+      std::cout << "     *Error Details* " << Result->Details << " \n";
+    }
+  }
+  return Result;
+}
+ol_result_t olGetDeviceCountWithCodeLoc(ol_platform_handle_t Platform,
+                                        uint32_t *NumDevices,
+                                        ol_code_location_t *CodeLocation) {
+  currentCodeLocation() = CodeLocation;
+  ol_result_t Result = olGetDeviceCount(Platform, NumDevices);
+
+  currentCodeLocation() = nullptr;
+  return Result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+ol_impl_result_t olGetDevice_val(ol_platform_handle_t Platform,
+                                 uint32_t NumEntries,
+                                 ol_device_handle_t *Devices) {
+  if (true /*enableParameterValidation*/) {
+    if (NumEntries == 0) {
+      return OL_ERRC_INVALID_SIZE;
+    }
+
+    if (NULL == Platform) {
+      return OL_ERRC_INVALID_NULL_HANDLE;
+    }
+
+    if (NULL == Devices) {
+      return OL_ERRC_INVALID_NULL_POINTER;
+    }
+  }
+
+  return olGetDevice_impl(Platform, NumEntries, Devices);
+}
+OL_APIEXPORT ol_result_t OL_APICALL olGetDevice(ol_platform_handle_t Platform,
+                                                uint32_t NumEntries,
+                                                ol_device_handle_t *Devices) {
+  if (offloadConfig().TracingEnabled) {
+    std::cout << "---> olGetDevice";
+  }
+
+  ol_result_t Result = olGetDevice_val(Platform, NumEntries, Devices);
+
+  if (offloadConfig().TracingEnabled) {
+    ol_get_device_params_t Params = {&Platform, &NumEntries, &Devices};
+    std::cout << "(" << &Params << ")";
+    std::cout << "-> " << Result << "\n";
+    if (Result && Result->Details) {
+      std::cout << "     *Error Details* " << Result->Details << " \n";
+    }
+  }
+  return Result;
+}
+ol_result_t olGetDeviceWithCodeLoc(ol_platform_handle_t Platform,
+                                   uint32_t NumEntries,
+                                   ol_device_handle_t *Devices,
+                                   ol_code_location_t *CodeLocation) {
+  currentCodeLocation() = CodeLocation;
+  ol_result_t Result = olGetDevice(Platform, NumEntries, Devices);
+
+  currentCodeLocation() = nullptr;
+  return Result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+ol_impl_result_t olGetDeviceInfo_val(ol_device_handle_t Device,
+                                     ol_device_info_t PropName, size_t PropSize,
+                                     void *PropValue) {
+  if (true /*enableParameterValidation*/) {
+    if (PropSize == 0) {
+      return OL_ERRC_INVALID_SIZE;
+    }
+
+    if (NULL == Device) {
+      return OL_ERRC_INVALID_NULL_HANDLE;
+    }
+
+    if (NULL == PropValue) {
+      return OL_ERRC_INVALID_NULL_POINTER;
+    }
+  }
+
+  return olGetDeviceInfo_impl(Device, PropName, PropSize, PropValue);
+}
+OL_APIEXPORT ol_result_t OL_APICALL olGetDeviceInfo(ol_device_handle_t Device,
+                                                    ol_device_info_t PropName,
+                                                    size_t PropSize,
+                                                    void *PropValue) {
+  if (offloadConfig().TracingEnabled) {
+    std::cout << "---> olGetDeviceInfo";
+  }
+
+  ol_result_t Result =
+      olGetDeviceInfo_val(Device, PropName, PropSize, PropValue);
+
+  if (offloadConfig().TracingEnabled) {
+    ol_get_device_info_params_t Params = {&Device, &PropName, &PropSize,
+                                          &PropValue};
+    std::cout << "(" << &Params << ")";
+    std::cout << "-> " << Result << "\n";
+    if (Result && Result->Details) {
+      std::cout << "     *Error Details* " << Result->Details << " \n";
+    }
+  }
+  return Result;
+}
+ol_result_t olGetDeviceInfoWithCodeLoc(ol_device_handle_t Device,
+                                       ol_device_info_t PropName,
+                                       size_t PropSize, void *PropValue,
+                                       ol_code_location_t *CodeLocation) {
+  currentCodeLocation() = CodeLocation;
+  ol_result_t Result = olGetDeviceInfo(Device, PropName, PropSize, PropValue);
+
+  currentCodeLocation() = nullptr;
+  return Result;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+ol_impl_result_t olGetDeviceInfoSize_val(ol_device_handle_t Device,
+                                         ol_device_info_t PropName,
+                                         size_t *PropSizeRet) {
+  if (true /*enableParameterValidation*/) {
+    if (NULL == Device) {
+      return OL_ERRC_INVALID_NULL_HANDLE;
+    }
+
+    if (NULL == PropSizeRet) {
+      return OL_ERRC_INVALID_NULL_POINTER;
+    }
+  }
+
+  return olGetDeviceInfoSize_impl(Device, PropName, PropSizeRet);
+}
+OL_APIEXPORT ol_result_t OL_APICALL olGetDeviceInfoSize(
+    ol_device_handle_t Device, ol_device_info_t PropName, size_t *PropSizeRet) {
+  if (offloadConfig().TracingEnabled) {
+    std::cout << "---> olGetDeviceInfoSize";
+  }
+
+  ol_result_t Result = olGetDeviceInfoSize_val(Device, PropName, PropSizeRet);
+
+  if (offloadConfig().TracingEnabled) {
+    ol_get_device_info_size_params_t Params = {&Device, &PropName,
+                                               &PropSizeRet};
+    std::cout << "(" << &Params << ")";
+    std::cout << "-> " << Result << "\n";
+    if (Result && Result->Details) {
+      std::cout << "     *Error Details* " << Result->Details << " \n";
+    }
+  }
+  return Result;
+}
+ol_result_t olGetDeviceInfoSizeWithCodeLoc(ol_device_handle_t Device,
+                                           ol_device_info_t PropName,
+                                           size_t *PropSizeRet,
+                                           ol_code_location_t *CodeLocation) {
+  currentCodeLocation() = CodeLocation;
+  ol_result_t Result = olGetDeviceInfoSize(Device, PropName, PropSizeRet);
+
+  currentCodeLocation() = nullptr;
+  return Result;
+}
diff --git a/offload/liboffload/include/generated/OffloadFuncs.inc b/offload/liboffload/include/generated/OffloadFuncs.inc
new file mode 100644
index 0000000000000..48115493c790f
--- /dev/null
+++ b/offload/liboffload/include/generated/OffloadFuncs.inc
@@ -0,0 +1,34 @@
+//===- Auto-generated file, part of the LLVM/Offload project --------------===//
+//
+// 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 OFFLOAD_FUNC
+#error Please define the macro OFFLOAD_FUNC(Function)
+#endif
+
+OFFLOAD_FUNC(olInit)
+OFFLOAD_FUNC(olShutDown)
+OFFLOAD_FUNC(olGetPlatform)
+OFFLOAD_FUNC(olGetPlatformCount)
+OFFLOAD_FUNC(olGetPlatformInfo)
+OFFLOAD_FUNC(olGetPlatformInfoSize)
+OFFLOAD_FUNC(olGetDeviceCount)
+OFFLOAD_FUNC(olGetDevice)
+OFFLOAD_FUNC(olGetDeviceInfo)
+OFFLOAD_FUNC(olGetDeviceInfoSize)
+OFFLOAD_FUNC(olInitWithCodeLoc)
+OFFLOAD_FUNC(olShutDownWithCodeLoc)
+OFFLOAD_FUNC(olGetPlatformWithCodeLoc)
+OFFLOAD_FUNC(olGetPlatformCountWithCodeLoc)
+OFFLOAD_FUNC(olGetPlatformInfoWithCodeLoc)
+OFFLOAD_FUNC(olGetPlatformInfoSizeWithCodeLoc)
+OFFLOAD_FUNC(olGetDeviceCountWithCodeLoc)
+OFFLOAD_FUNC(olGetDeviceWithCodeLoc)
+OFFLOAD_FUNC(olGetDeviceInfoWithCodeLoc)
+OFFLOAD_FUNC(olGetDeviceInfoSizeWithCodeLoc)
+
+#undef OFFLOAD_FUNC
diff --git a/offload/liboffload/include/generated/OffloadImplFuncDecls.inc b/offload/liboffload/include/generated/OffloadImplFuncDecls.inc
new file mode 100644
index 0000000000000..5b26b2653a05d
--- /dev/null
+++ b/offload/liboffload/include/generated/OffloadImplFuncDecls.inc
@@ -0,0 +1,38 @@
+//===- Auto-generated file, part of the LLVM/Offload project --------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+ol_impl_result_t olInit_impl();
+
+ol_impl_result_t olShutDown_impl();
+
+ol_impl_result_t olGetPlatform_impl(uint32_t NumEntries,
+                                    ol_platform_handle_t *Platforms);
+
+ol_impl_result_t olGetPlatformCount_impl(uint32_t *NumPlatforms);
+
+ol_impl_result_t olGetPlatformInfo_impl(ol_platform_handle_t Platform,
+                                        ol_platform_info_t PropName,
+                                        size_t PropSize, void *PropValue);
+
+ol_impl_result_t olGetPlatformInfoSize_impl(ol_platform_handle_t Platform,
+                                            ol_platform_info_t PropName,
+                                            size_t *PropSizeRet);
+
+ol_impl_result_t olGetDeviceCount_impl(ol_platform_handle_t Platform,
+                                       uint32_t *NumDevices);
+
+ol_impl_result_t olGetDevice_impl(ol_platform_handle_t Platform,
+                                  uint32_t NumEntries,
+                                  ol_device_handle_t *Devices);
+
+ol_impl_result_t olGetDeviceInfo_impl(ol_device_handle_t Device,
+                                      ol_device_info_t PropName,
+                                      size_t PropSize, void *PropValue);
+
+ol_impl_result_t olGetDeviceInfoSize_impl(ol_device_handle_t Device,
+                                          ol_device_info_t PropName,
+                                          size_t *PropSizeRet);
diff --git a/offload/liboffload/include/generated/OffloadPrint.hpp b/offload/liboffload/include/generated/OffloadPrint.hpp
new file mode 100644
index 0000000000000..8981bb054a4cb
--- /dev/null
+++ b/offload/liboffload/include/generated/OffloadPrint.hpp
@@ -0,0 +1,428 @@
+//===- Auto-generated file, part of the LLVM/Offload project --------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Auto-generated file, do not manually edit.
+
+#pragma once
+
+#include <OffloadAPI.h>
+#include <ostream>
+
+template <typename T>
+inline ol_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);
+template <typename T> struct is_handle : std::false_type {};
+template <> struct is_handle<ol_platform_handle_t> : std::true_type {};
+template <> struct is_handle<ol_device_handle_t> : std::true_type {};
+template <> struct is_handle<ol_context_handle_t> : std::true_type {};
+template <typename T> inline constexpr bool is_handle_v = is_handle<T>::value;
+
+inline std::ostream &operator<<(std::ostream &os, enum ol_errc_t value);
+inline std::ostream &operator<<(std::ostream &os,
+                                enum ol_platform_info_t value);
+inline std::ostream &operator<<(std::ostream &os,
+                                enum ol_platform_backend_t value);
+inline std::ostream &operator<<(std::ostream &os, enum ol_device_type_t value);
+inline std::ostream &operator<<(std::ostream &os, enum ol_device_info_t value);
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Print operator for the ol_errc_t type
+/// @returns std::ostream &
+inline std::ostream &operator<<(std::ostream &os, enum ol_errc_t value) {
+  switch (value) {
+  case OL_ERRC_SUCCESS:
+    os << "OL_ERRC_SUCCESS";
+    break;
+  case OL_ERRC_INVALID_VALUE:
+    os << "OL_ERRC_INVALID_VALUE";
+    break;
+  case OL_ERRC_INVALID_PLATFORM:
+    os << "OL_ERRC_INVALID_PLATFORM";
+    break;
+  case OL_ERRC_DEVICE_NOT_FOUND:
+    os << "OL_ERRC_DEVICE_NOT_FOUND";
+    break;
+  case OL_ERRC_INVALID_DEVICE:
+    os << "OL_ERRC_INVALID_DEVICE";
+    break;
+  case OL_ERRC_DEVICE_LOST:
+    os << "OL_ERRC_DEVICE_LOST";
+    break;
+  case OL_ERRC_UNINITIALIZED:
+    os << "OL_ERRC_UNINITIALIZED";
+    break;
+  case OL_ERRC_OUT_OF_RESOURCES:
+    os << "OL_ERRC_OUT_OF_RESOURCES";
+    break;
+  case OL_ERRC_UNSUPPORTED_VERSION:
+    os << "OL_ERRC_UNSUPPORTED_VERSION";
+    break;
+  case OL_ERRC_UNSUPPORTED_FEATURE:
+    os << "OL_ERRC_UNSUPPORTED_FEATURE";
+    break;
+  case OL_ERRC_INVALID_ARGUMENT:
+    os << "OL_ERRC_INVALID_ARGUMENT";
+    break;
+  case OL_ERRC_INVALID_NULL_HANDLE:
+    os << "OL_ERRC_INVALID_NULL_HANDLE";
+    break;
+  case OL_ERRC_INVALID_NULL_POINTER:
+    os << "OL_ERRC_INVALID_NULL_POINTER";
+    break;
+  case OL_ERRC_INVALID_SIZE:
+    os << "OL_ERRC_INVALID_SIZE";
+    break;
+  case OL_ERRC_INVALID_ENUMERATION:
+    os << "OL_ERRC_INVALID_ENUMERATION";
+    break;
+  case OL_ERRC_UNSUPPORTED_ENUMERATION:
+    os << "OL_ERRC_UNSUPPORTED_ENUMERATION";
+    break;
+  case OL_ERRC_UNKNOWN:
+    os << "OL_ERRC_UNKNOWN";
+    break;
+  default:
+    os << "unknown enumerator";
+    break;
+  }
+  return os;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Print operator for the ol_platform_info_t type
+/// @returns std::ostream &
+inline std::ostream &operator<<(std::ostream &os,
+                                enum ol_platform_info_t value) {
+  switch (value) {
+  case OL_PLATFORM_INFO_NAME:
+    os << "OL_PLATFORM_INFO_NAME";
+    break;
+  case OL_PLATFORM_INFO_VENDOR_NAME:
+    os << "OL_PLATFORM_INFO_VENDOR_NAME";
+    break;
+  case OL_PLATFORM_INFO_VERSION:
+    os << "OL_PLATFORM_INFO_VERSION";
+    break;
+  case OL_PLATFORM_INFO_BACKEND:
+    os << "OL_PLATFORM_INFO_BACKEND";
+    break;
+  default:
+    os << "unknown enumerator";
+    break;
+  }
+  return os;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Print type-tagged ol_platform_info_t enum value
+/// @returns std::ostream &
+template <>
+inline void printTagged(std::ostream &os, const void *ptr,
+                        ol_platform_info_t value, size_t size) {
+  if (ptr == NULL) {
+    printPtr(os, ptr);
+    return;
+  }
+
+  switch (value) {
+  case OL_PLATFORM_INFO_NAME: {
+    printPtr(os, (const char *)ptr);
+    break;
+  }
+  case OL_PLATFORM_INFO_VENDOR_NAME: {
+    printPtr(os, (const char *)ptr);
+    break;
+  }
+  case OL_PLATFORM_INFO_VERSION: {
+    printPtr(os, (const char *)ptr);
+    break;
+  }
+  case OL_PLATFORM_INFO_BACKEND: {
+    const ol_platform_backend_t *const tptr =
+        (const ol_platform_backend_t *const)ptr;
+    os << (const void *)tptr << " (";
+    os << *tptr;
+    os << ")";
+    break;
+  }
+  default:
+    os << "unknown enumerator";
+    break;
+  }
+}
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Print operator for the ol_platform_backend_t type
+/// @returns std::ostream &
+inline std::ostream &operator<<(std::ostream &os,
+                                enum ol_platform_backend_t value) {
+  switch (value) {
+  case OL_PLATFORM_BACKEND_UNKNOWN:
+    os << "OL_PLATFORM_BACKEND_UNKNOWN";
+    break;
+  case OL_PLATFORM_BACKEND_CUDA:
+    os << "OL_PLATFORM_BACKEND_CUDA";
+    break;
+  case OL_PLATFORM_BACKEND_AMDGPU:
+    os << "OL_PLATFORM_BACKEND_AMDGPU";
+    break;
+  default:
+    os << "unknown enumerator";
+    break;
+  }
+  return os;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Print operator for the ol_device_type_t type
+/// @returns std::ostream &
+inline std::ostream &operator<<(std::ostream &os, enum ol_device_type_t value) {
+  switch (value) {
+  case OL_DEVICE_TYPE_DEFAULT:
+    os << "OL_DEVICE_TYPE_DEFAULT";
+    break;
+  case OL_DEVICE_TYPE_ALL:
+    os << "OL_DEVICE_TYPE_ALL";
+    break;
+  case OL_DEVICE_TYPE_GPU:
+    os << "OL_DEVICE_TYPE_GPU";
+    break;
+  case OL_DEVICE_TYPE_CPU:
+    os << "OL_DEVICE_TYPE_CPU";
+    break;
+  default:
+    os << "unknown enumerator";
+    break;
+  }
+  return os;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Print operator for the ol_device_info_t type
+/// @returns std::ostream &
+inline std::ostream &operator<<(std::ostream &os, enum ol_device_info_t value) {
+  switch (value) {
+  case OL_DEVICE_INFO_TYPE:
+    os << "OL_DEVICE_INFO_TYPE";
+    break;
+  case OL_DEVICE_INFO_PLATFORM:
+    os << "OL_DEVICE_INFO_PLATFORM";
+    break;
+  case OL_DEVICE_INFO_NAME:
+    os << "OL_DEVICE_INFO_NAME";
+    break;
+  case OL_DEVICE_INFO_VENDOR:
+    os << "OL_DEVICE_INFO_VENDOR";
+    break;
+  case OL_DEVICE_INFO_DRIVER_VERSION:
+    os << "OL_DEVICE_INFO_DRIVER_VERSION";
+    break;
+  default:
+    os << "unknown enumerator";
+    break;
+  }
+  return os;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Print type-tagged ol_device_info_t enum value
+/// @returns std::ostream &
+template <>
+inline void printTagged(std::ostream &os, const void *ptr,
+                        ol_device_info_t value, size_t size) {
+  if (ptr == NULL) {
+    printPtr(os, ptr);
+    return;
+  }
+
+  switch (value) {
+  case OL_DEVICE_INFO_TYPE: {
+    const ol_device_type_t *const tptr = (const ol_device_type_t *const)ptr;
+    os << (const void *)tptr << " (";
+    os << *tptr;
+    os << ")";
+    break;
+  }
+  case OL_DEVICE_INFO_PLATFORM: {
+    const ol_platform_handle_t *const tptr =
+        (const ol_platform_handle_t *const)ptr;
+    os << (const void *)tptr << " (";
+    os << *tptr;
+    os << ")";
+    break;
+  }
+  case OL_DEVICE_INFO_NAME: {
+    printPtr(os, (const char *)ptr);
+    break;
+  }
+  case OL_DEVICE_INFO_VENDOR: {
+    printPtr(os, (const char *)ptr);
+    break;
+  }
+  case OL_DEVICE_INFO_DRIVER_VERSION: {
+    printPtr(os, (const char *)ptr);
+    break;
+  }
+  default:
+    os << "unknown enumerator";
+    break;
+  }
+}
+
+inline std::ostream &operator<<(std::ostream &os,
+                                const ol_error_struct_t *Err) {
+  if (Err == nullptr) {
+    os << "OL_SUCCESS";
+  } else {
+    os << Err->Code;
+  }
+  return os;
+}
+
+inline std::ostream &operator<<(std::ostream &os,
+                                const struct ol_get_platform_params_t *params) {
+  os << ".NumEntries = ";
+  os << *params->pNumEntries;
+  os << ", ";
+  os << ".Platforms = ";
+  os << "{";
+  for (size_t i = 0; i < *params->pNumEntries; i++) {
+    if (i > 0) {
+      os << ", ";
+    }
+    printPtr(os, (*params->pPlatforms)[i]);
+  }
+  os << "}";
+  return os;
+}
+
+inline std::ostream &
+operator<<(std::ostream &os,
+           const struct ol_get_platform_count_params_t *params) {
+  os << ".NumPlatforms = ";
+  printPtr(os, *params->pNumPlatforms);
+  return os;
+}
+
+inline std::ostream &
+operator<<(std::ostream &os,
+           const struct ol_get_platform_info_params_t *params) {
+  os << ".Platform = ";
+  printPtr(os, *params->pPlatform);
+  os << ", ";
+  os << ".PropName = ";
+  os << *params->pPropName;
+  os << ", ";
+  os << ".PropSize = ";
+  os << *params->pPropSize;
+  os << ", ";
+  os << ".PropValue = ";
+  printTagged(os, *params->pPropValue, *params->pPropName, *params->pPropSize);
+  return os;
+}
+
+inline std::ostream &
+operator<<(std::ostream &os,
+           const struct ol_get_platform_info_size_params_t *params) {
+  os << ".Platform = ";
+  printPtr(os, *params->pPlatform);
+  os << ", ";
+  os << ".PropName = ";
+  os << *params->pPropName;
+  os << ", ";
+  os << ".PropSizeRet = ";
+  printPtr(os, *params->pPropSizeRet);
+  return os;
+}
+
+inline std::ostream &
+operator<<(std::ostream &os,
+           const struct ol_get_device_count_params_t *params) {
+  os << ".Platform = ";
+  printPtr(os, *params->pPlatform);
+  os << ", ";
+  os << ".NumDevices = ";
+  printPtr(os, *params->pNumDevices);
+  return os;
+}
+
+inline std::ostream &operator<<(std::ostream &os,
+                                const struct ol_get_device_params_t *params) {
+  os << ".Platform = ";
+  printPtr(os, *params->pPlatform);
+  os << ", ";
+  os << ".NumEntries = ";
+  os << *params->pNumEntries;
+  os << ", ";
+  os << ".Devices = ";
+  os << "{";
+  for (size_t i = 0; i < *params->pNumEntries; i++) {
+    if (i > 0) {
+      os << ", ";
+    }
+    printPtr(os, (*params->pDevices)[i]);
+  }
+  os << "}";
+  return os;
+}
+
+inline std::ostream &
+operator<<(std::ostream &os, const struct ol_get_device_info_params_t *params) {
+  os << ".Device = ";
+  printPtr(os, *params->pDevice);
+  os << ", ";
+  os << ".PropName = ";
+  os << *params->pPropName;
+  os << ", ";
+  os << ".PropSize = ";
+  os << *params->pPropSize;
+  os << ", ";
+  os << ".PropValue = ";
+  printTagged(os, *params->pPropValue, *params->pPropName, *params->pPropSize);
+  return os;
+}
+
+inline std::ostream &
+operator<<(std::ostream &os,
+           const struct ol_get_device_info_size_params_t *params) {
+  os << ".Device = ";
+  printPtr(os, *params->pDevice);
+  os << ", ";
+  os << ".PropName = ";
+  os << *params->pPropName;
+  os << ", ";
+  os << ".PropSizeRet = ";
+  printPtr(os, *params->pPropSizeRet);
+  return os;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// @brief Print pointer value
+template <typename T>
+inline ol_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 OL_SUCCESS;
+}
diff --git a/offload/liboffload/src/Helpers.hpp b/offload/liboffload/src/Helpers.hpp
new file mode 100644
index 0000000000000..d003d30252462
--- /dev/null
+++ b/offload/liboffload/src/Helpers.hpp
@@ -0,0 +1,95 @@
+//===- 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 "OffloadAPI.h"
+
+#include <cstring>
+
+template <typename T, typename Assign>
+ol_errc_t getInfoImpl(size_t ParamValueSize, void *ParamValue,
+                      size_t *ParamValueSizeRet, T Value, size_t ValueSize,
+                      Assign &&AssignFunc) {
+  if (!ParamValue && !ParamValueSizeRet) {
+    return OL_ERRC_INVALID_NULL_POINTER;
+  }
+
+  if (ParamValue != nullptr) {
+    if (ParamValueSize < ValueSize) {
+      return OL_ERRC_INVALID_SIZE;
+    }
+    AssignFunc(ParamValue, Value, ValueSize);
+  }
+
+  if (ParamValueSizeRet != nullptr) {
+    *ParamValueSizeRet = ValueSize;
+  }
+
+  return OL_ERRC_SUCCESS;
+}
+
+template <typename T>
+ol_errc_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>
+ol_errc_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 ol_errc_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> ol_errc_t operator()(const T &t) {
+    return getInfo(ParamValueSize, ParamValue, ParamValueSizeRet, t);
+  }
+
+  // Array return Value
+  template <class T> ol_errc_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/liboffload/src/OffloadImpl.cpp b/offload/liboffload/src/OffloadImpl.cpp
new file mode 100644
index 0000000000000..457f1053f1634
--- /dev/null
+++ b/offload/liboffload/src/OffloadImpl.cpp
@@ -0,0 +1,247 @@
+//===- ol_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 "OffloadImpl.hpp"
+#include "Helpers.hpp"
+#include "PluginManager.h"
+#include "llvm/Support/FormatVariadic.h"
+#include <OffloadAPI.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 ol_device_handle_t_ {
+  int DeviceNum;
+  GenericDeviceTy &Device;
+  ol_platform_handle_t Platform;
+};
+
+struct ol_platform_handle_t_ {
+  std::unique_ptr<GenericPluginTy> Plugin;
+  std::vector<ol_device_handle_t_> Devices;
+};
+
+using PlatformVecT = SmallVector<ol_platform_handle_t_, 4>;
+PlatformVecT &Platforms() {
+  static PlatformVecT Platforms;
+  return Platforms;
+}
+
+// TODO: Some plugins expect to be linked into libomptarget which defines these
+// symbols to implement ompt callbacks. The least invasive workaround here is to
+// define them in libLLVMOffload as false/null so they are never used. In future
+// it would be better to allow the plugins to implement callbacks without
+// pulling in details from libomptarget.
+#ifdef OMPT_SUPPORT
+namespace llvm::omp::target {
+namespace ompt {
+bool Initialized = false;
+ompt_get_callback_t lookupCallbackByCode = nullptr;
+ompt_function_lookup_t lookupCallbackByName = nullptr;
+} // namespace ompt
+} // namespace llvm::omp::target
+#endif
+
+// 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(ol_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(ol_device_handle_t_{
+            DevNum, Platform.Plugin->getDevice(DevNum), &Platform});
+      }
+    }
+  }
+
+  offloadConfig().TracingEnabled = std::getenv("OFFLOAD_TRACE");
+}
+
+// TODO: We can properly reference count here and manage the resources in a more
+// clever way
+ol_impl_result_t olInit_impl() {
+  static std::once_flag InitFlag;
+  std::call_once(InitFlag, initPlugins);
+
+  return OL_SUCCESS;
+}
+ol_impl_result_t olShutDown_impl() { return OL_SUCCESS; }
+
+ol_impl_result_t olGetPlatformCount_impl(uint32_t *NumPlatforms) {
+  *NumPlatforms = Platforms().size();
+  return OL_SUCCESS;
+}
+
+ol_impl_result_t olGetPlatform_impl(uint32_t NumEntries,
+                                    ol_platform_handle_t *PlatformsOut) {
+  if (NumEntries > Platforms().size()) {
+    return {OL_ERRC_INVALID_SIZE,
+            std::string{formatv("{0} platform(s) available but {1} requested.",
+                                Platforms().size(), NumEntries)}};
+  }
+
+  for (uint32_t PlatformIndex = 0; PlatformIndex < NumEntries;
+       PlatformIndex++) {
+    PlatformsOut[PlatformIndex] = &(Platforms())[PlatformIndex];
+  }
+
+  return OL_SUCCESS;
+}
+
+ol_impl_result_t olGetPlatformInfoImplDetail(ol_platform_handle_t Platform,
+                                             ol_platform_info_t PropName,
+                                             size_t PropSize, void *PropValue,
+                                             size_t *PropSizeRet) {
+  ReturnHelper ReturnValue(PropSize, PropValue, PropSizeRet);
+
+  switch (PropName) {
+  case OL_PLATFORM_INFO_NAME:
+    return ReturnValue(Platform->Plugin->getName());
+  case OL_PLATFORM_INFO_VENDOR_NAME:
+    // TODO: Implement this
+    return ReturnValue("Unknown platform vendor");
+  case OL_PLATFORM_INFO_VERSION: {
+    return ReturnValue(formatv("v{0}.{1}.{2}", OL_VERSION_MAJOR,
+                               OL_VERSION_MINOR, OL_VERSION_PATCH)
+                           .str()
+                           .c_str());
+  }
+  case OL_PLATFORM_INFO_BACKEND: {
+    auto PluginName = Platform->Plugin->getName();
+    if (PluginName == StringRef("CUDA")) {
+      return ReturnValue(OL_PLATFORM_BACKEND_CUDA);
+    } else if (PluginName == StringRef("AMDGPU")) {
+      return ReturnValue(OL_PLATFORM_BACKEND_AMDGPU);
+    } else {
+      return ReturnValue(OL_PLATFORM_BACKEND_UNKNOWN);
+    }
+  }
+  default:
+    return OL_ERRC_INVALID_ENUMERATION;
+  }
+
+  return OL_SUCCESS;
+}
+
+ol_impl_result_t olGetPlatformInfo_impl(ol_platform_handle_t Platform,
+                                        ol_platform_info_t PropName,
+                                        size_t PropSize, void *PropValue) {
+  return olGetPlatformInfoImplDetail(Platform, PropName, PropSize, PropValue,
+                                     nullptr);
+}
+
+ol_impl_result_t olGetPlatformInfoSize_impl(ol_platform_handle_t Platform,
+                                            ol_platform_info_t PropName,
+                                            size_t *PropSizeRet) {
+  return olGetPlatformInfoImplDetail(Platform, PropName, 0, nullptr,
+                                     PropSizeRet);
+}
+
+ol_impl_result_t olGetDeviceCount_impl(ol_platform_handle_t Platform,
+                                       uint32_t *pNumDevices) {
+  *pNumDevices = static_cast<uint32_t>(Platform->Devices.size());
+
+  return OL_SUCCESS;
+}
+
+ol_impl_result_t olGetDevice_impl(ol_platform_handle_t Platform,
+                                  uint32_t NumEntries,
+                                  ol_device_handle_t *Devices) {
+  if (NumEntries > Platform->Devices.size()) {
+    return OL_ERRC_INVALID_SIZE;
+  }
+
+  for (uint32_t DeviceIndex = 0; DeviceIndex < NumEntries; DeviceIndex++) {
+    Devices[DeviceIndex] = &(Platform->Devices[DeviceIndex]);
+  }
+
+  return OL_SUCCESS;
+}
+
+ol_impl_result_t olGetDeviceInfoImplDetail(ol_device_handle_t Device,
+                                           ol_device_info_t PropName,
+                                           size_t PropSize, void *PropValue,
+                                           size_t *PropSizeRet) {
+
+  ReturnHelper ReturnValue(PropSize, PropValue, PropSizeRet);
+
+  InfoQueueTy DevInfo;
+  if (auto Err = Device->Device.obtainInfoImpl(DevInfo))
+    return OL_ERRC_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 OL_DEVICE_INFO_PLATFORM:
+    return ReturnValue(Device->Platform);
+  case OL_DEVICE_INFO_TYPE:
+    return ReturnValue(OL_DEVICE_TYPE_GPU);
+  case OL_DEVICE_INFO_NAME:
+    return ReturnValue(GetInfo({"Device Name"}).c_str());
+  case OL_DEVICE_INFO_VENDOR:
+    return ReturnValue(GetInfo({"Vendor Name"}).c_str());
+  case OL_DEVICE_INFO_DRIVER_VERSION:
+    return ReturnValue(
+        GetInfo({"CUDA Driver Version", "HSA Runtime Version"}).c_str());
+  default:
+    return OL_ERRC_INVALID_ENUMERATION;
+  }
+
+  return OL_SUCCESS;
+}
+
+ol_impl_result_t olGetDeviceInfo_impl(ol_device_handle_t Device,
+                                      ol_device_info_t PropName,
+                                      size_t PropSize, void *PropValue) {
+  return olGetDeviceInfoImplDetail(Device, PropName, PropSize, PropValue,
+                                   nullptr);
+}
+
+ol_impl_result_t olGetDeviceInfoSize_impl(ol_device_handle_t Device,
+                                          ol_device_info_t PropName,
+                                          size_t *PropSizeRet) {
+  return olGetDeviceInfoImplDetail(Device, PropName, 0, nullptr, PropSizeRet);
+}
diff --git a/offload/liboffload/src/OffloadLib.cpp b/offload/liboffload/src/OffloadLib.cpp
new file mode 100644
index 0000000000000..37876713212c9
--- /dev/null
+++ b/offload/liboffload/src/OffloadLib.cpp
@@ -0,0 +1,44 @@
+//===- 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 "OffloadImpl.hpp"
+#include <OffloadAPI.h>
+#include <OffloadPrint.hpp>
+
+#include <iostream>
+
+llvm::StringSet<> &errorStrs() {
+  static llvm::StringSet<> ErrorStrs;
+  return ErrorStrs;
+}
+
+ErrSetT &errors() {
+  static ErrSetT Errors{};
+  return Errors;
+}
+
+ol_code_location_t *&currentCodeLocation() {
+  thread_local ol_code_location_t *CodeLoc = nullptr;
+  return CodeLoc;
+}
+
+OffloadConfig &offloadConfig() {
+  static OffloadConfig Config{};
+  return Config;
+}
+
+// Pull in the declarations for the implementation funtions. The actual entry
+// points in this file wrap these.
+#include "OffloadImplFuncDecls.inc"
+
+// Pull in the tablegen'd entry point definitions.
+#include "OffloadEntryPoints.inc"
diff --git a/offload/plugins-nextgen/common/include/PluginInterface.h b/offload/plugins-nextgen/common/include/PluginInterface.h
index 41cc0f286a581..82efcdaf5ad3d 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,7 @@ class InfoQueueTy {
     uint64_t Level;
   };
 
+private:
   std::deque<InfoQueueEntryTy> Queue;
 
 public:
@@ -153,6 +155,8 @@ 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/test/lit.cfg b/offload/test/lit.cfg
index 2f1ef3e98d817..658ae5f9653ba 100644
--- a/offload/test/lit.cfg
+++ b/offload/test/lit.cfg
@@ -66,7 +66,7 @@ def evaluate_bool_env(env):
 config.name = 'libomptarget :: ' + config.libomptarget_current_target
 
 # suffixes: A list of file extensions to treat as test files.
-config.suffixes = ['.c', '.cpp', '.cc', '.f90', '.cu']
+config.suffixes = ['.c', '.cpp', '.cc', '.f90', '.cu', '.td']
 
 # excludes: A list of directories to exclude from the testuites.
 config.excludes = ['Inputs']
@@ -418,3 +418,4 @@ config.substitutions.append(("%flags", config.test_flags))
 config.substitutions.append(("%not", config.libomptarget_not))
 config.substitutions.append(("%offload-device-info",
                              config.offload_device_info))
+config.substitutions.append(("%offload-tblgen", config.offload_tblgen))
diff --git a/offload/test/lit.site.cfg.in b/offload/test/lit.site.cfg.in
index a1cb5acc38a40..ce3f6abf50a13 100644
--- a/offload/test/lit.site.cfg.in
+++ b/offload/test/lit.site.cfg.in
@@ -28,5 +28,6 @@ config.libomptarget_debug = @LIBOMPTARGET_DEBUG@
 config.has_libomptarget_ompt = @LIBOMPTARGET_OMPT_SUPPORT@
 config.libomptarget_has_libc = @LIBOMPTARGET_GPU_LIBC_SUPPORT@
 config.libomptarget_test_pgo = @LIBOMPTARGET_TEST_GPU_PGO@
+config.offload_tblgen = "@OFFLOAD_TBLGEN_EXECUTABLE@"
 # Let the main config do the real work.
 lit_config.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg")
diff --git a/offload/test/tools/offload-tblgen/default_returns.td b/offload/test/tools/offload-tblgen/default_returns.td
new file mode 100644
index 0000000000000..432063e0174af
--- /dev/null
+++ b/offload/test/tools/offload-tblgen/default_returns.td
@@ -0,0 +1,40 @@
+// RUN: %offload-tblgen -gen-api -I %S/../../../new-api/API %s | %fcheck-generic --check-prefix=CHECK-API
+// RUN: %offload-tblgen -gen-entry-points -I %S/../../../new-api/API %s | %fcheck-generic --check-prefix=CHECK-VALIDATION
+
+// Check implicit returns are included in documentation and the validation
+// wrappers where applicable
+
+include "APIDefs.td"
+
+def : Handle {
+    let name = "offload_foo_handle_t";
+    let desc = "Example handle type";
+}
+
+def : Function {
+    let name = "FunctionA";
+    let desc = "Function A description";
+    let details = [ "Function A detailed information" ];
+    let params = [
+        Param<"uint32_t", "ParamValue", "A plain value parameter">,
+        Param<"offload_foo_handle_t", "ParamHandle", "A handle parameter">,
+        Param<"uint32_t*", "ParamPointer", "A pointer parameter">,
+        Param<"uint32_t*", "ParamPointerOpt", "An optional pointer parameter", PARAM_OUT_OPTIONAL>
+    ];
+    let returns = [];
+}
+
+// CHECK-API: /// @returns
+// CHECK-API: OFFLOAD_RESULT_SUCCESS
+// CHECK-API: OFFLOAD_ERRC_INVALID_NULL_HANDLE
+// CHECK-API-NEXT: `NULL == ParamHandle`
+// CHECK-API: OFFLOAD_ERRC_INVALID_NULL_POINTER
+// CHECK-API-NEXT: `NULL == ParamPointer`
+// CHECK-API-NOT: `NULL == ParamPointerOpt`
+
+// CHECK-VALIDATION: FunctionA_val
+// CHECK-VALIDATION: if (NULL == ParamHandle)
+// CHECK-VALIDATION-NEXT: return OFFLOAD_ERRC_INVALID_NULL_HANDLE;
+// CHECK-VALIDATION: if (NULL == ParamPointer)
+// CHECK-VALIDATION-NEXT: return OFFLOAD_ERRC_INVALID_NULL_POINTER;
+// CHECK-VALIDATION-NOT: if (NULL == ParamPointerOpt)
diff --git a/offload/test/tools/offload-tblgen/entry_points.td b/offload/test/tools/offload-tblgen/entry_points.td
new file mode 100644
index 0000000000000..2d2bd1f5e3bfc
--- /dev/null
+++ b/offload/test/tools/offload-tblgen/entry_points.td
@@ -0,0 +1,37 @@
+// RUN: %offload-tblgen -gen-entry-points -I %S/../../../new-api/API %s | %fcheck-generic
+
+// Check entry point wrapper functions are generated correctly
+
+include "APIDefs.td"
+
+def : Function {
+    let name = "FunctionA";
+    let desc = "Function A description";
+    let details = [ "Function A detailed information" ];
+    let params = [
+        Param<"uint32_t", "ParamA", "Parameter A description">,
+        Param<"uint32_t*", "ParamB", "Parameter B description">,
+    ];
+    let returns = [
+        Return<"OFFLOAD_ERRC_INVALID_VALUE", ["When a value is invalid"]>
+    ];
+}
+
+
+// The validation function should call the implementation function
+// CHECK: FunctionA_val
+// CHECK: return FunctionA_impl(ParamA, ParamB);
+
+// CHECK: offload_result_t{{.*}} FunctionA(
+
+// The entry point should print tracing output if enabled
+// CHECK: if (offloadConfig().TracingEnabled) {
+// CHECK-NEXT: "---> FunctionA";
+
+// CHECK: Result = FunctionA_val(ParamA, ParamB);
+
+// Tracing should construct a param struct for printing
+// CHECK: if (offloadConfig().TracingEnabled) {
+// CHECK: function_a_params_t Params = {&ParamA, &ParamB};
+
+// CHECK: return Result;
diff --git a/offload/test/tools/offload-tblgen/functions_basic.td b/offload/test/tools/offload-tblgen/functions_basic.td
new file mode 100644
index 0000000000000..6601746a727b0
--- /dev/null
+++ b/offload/test/tools/offload-tblgen/functions_basic.td
@@ -0,0 +1,39 @@
+// RUN: %offload-tblgen -gen-api -I %S/../../../new-api/API %s | %fcheck-generic --check-prefix=CHECK-API
+// RUN: %offload-tblgen -gen-exports -I %S/../../../new-api/API %s | %fcheck-generic --check-prefix=CHECK-EXPORTS
+// RUN: %offload-tblgen -gen-func-names -I %S/../../../new-api/API %s | %fcheck-generic --check-prefix=CHECK-FUNC-MACRO
+
+// Check basic support for API functions
+
+include "APIDefs.td"
+
+def : Function {
+    let name = "FunctionA";
+    let desc = "Function A description";
+    let details = [ "Function A detailed information" ];
+    let params = [
+        Param<"uint32_t", "ParamA", "Parameter A description">,
+        Param<"uint32_t*", "ParamB", "Parameter B description">,
+    ];
+    let returns = [
+        Return<"OFFLOAD_ERRC_INVALID_VALUE", ["When a value is invalid"]>
+    ];
+}
+
+// CHECK-API: /// @brief Function A description
+// CHECK-API: /// @details
+// CHECK-API-NEXT: Function A detailed information
+// CHECK-API: /// @returns
+// CHECK-API: OFFLOAD_ERRC_INVALID_VALUE
+// CHECK-API-NEXT: When a value is invalid
+
+// CHECK-API: offload_result_t
+// CHECK-API-SAME: FunctionA
+
+// CHECK-API: // Parameter A description
+// CHECK-API-NEXT: uint32_t ParamA
+// CHECK-API: // Parameter B description
+// CHECK-API-NEXT: uint32_t* ParamB
+
+// CHECK-EXPORTS: FunctionA
+
+// CHECK-FUNC-MACRO: OFFLOAD_FUNC(FunctionA)
diff --git a/offload/test/tools/offload-tblgen/functions_code_loc.td b/offload/test/tools/offload-tblgen/functions_code_loc.td
new file mode 100644
index 0000000000000..4c8d3688566c3
--- /dev/null
+++ b/offload/test/tools/offload-tblgen/functions_code_loc.td
@@ -0,0 +1,26 @@
+// RUN: %offload-tblgen -gen-api -I %S/../../../new-api/API %s | %fcheck-generic --check-prefix=CHECK-API
+// RUN: %offload-tblgen -gen-exports -I %S/../../../new-api/API %s | %fcheck-generic --check-prefix=CHECK-EXPORTS
+// RUN: %offload-tblgen -gen-func-names -I %S/../../../new-api/API %s | %fcheck-generic --check-prefix=CHECK-FUNC-MACRO
+
+// Check that the function variant with code location information is generated
+// and is otherwise the same as the regular function
+
+include "APIDefs.td"
+
+def : Function {
+    let name = "FunctionA";
+    let desc = "Function A description";
+    let details = [ "Function A detailed information" ];
+    let params = [
+        Param<"uint32_t", "ParamA", "Parameter A description">,
+        Param<"uint32_t*", "ParamB", "Parameter B description">,
+    ];
+    let returns = [
+        Return<"OFFLOAD_ERRC_INVALID_VALUE", ["When a value is invalid"]>
+    ];
+}
+
+// CHECK-API-DAG: offload_result_t{{.*}} FunctionA
+// CHECK-API-DAG: offload_result_t{{.*}} FunctionAWithCodeLoc
+// CHECK-EXPORTS: FunctionAWithCodeLoc
+// CHECK-FUNC-MACRO: OFFLOAD_FUNC(FunctionAWithCodeLoc)
diff --git a/offload/test/tools/offload-tblgen/functions_ranged_param.td b/offload/test/tools/offload-tblgen/functions_ranged_param.td
new file mode 100644
index 0000000000000..efa8bae5290ec
--- /dev/null
+++ b/offload/test/tools/offload-tblgen/functions_ranged_param.td
@@ -0,0 +1,36 @@
+// RUN: %offload-tblgen -gen-print-header -I %S/../../../new-api/API %s | %fcheck-generic
+
+// Check that ranged function parameters are implemented correctly. These
+// are pointers to an array of an arbitrary size. Their size is described as a
+// range between two values. This is typically between 0 and a parameter such
+// as NumItems. The range information helps the printing code print the entire
+// range of the output rather than just the pointer or the first element.
+
+include "APIDefs.td"
+
+def : Handle {
+    let name = "some_handle_t";
+    let desc = "An example handle type";
+}
+
+def : Function {
+    let name = "FunctionA";
+    let desc = "Function A description";
+    let details = [ "Function A detailed information" ];
+  let params = [
+    Param<"size_t", "OutCount", "the number of things to write out", PARAM_IN>,
+    RangedParam<"some_handle_t*", "OutPtr", "pointer to the output things.", PARAM_OUT,
+      Range<"0", "OutCount">>
+  ];
+  let returns = [];
+}
+
+// CHECK: inline std::ostream &operator<<(std::ostream &os, const struct function_a_params_t *params) {
+// CHECK:   os << ".OutPtr = ";
+// CHECK:   for (size_t i = 0; i < *params->pOutCount; i++) {
+// CHECK:     if (i > 0) {
+// CHECK:       os << ", ";
+// CHECK:     }
+// CHECK:     printPtr(os, (*params->pOutPtr)[i]);
+// CHECK:   }
+// CHECK:   os << "}";
diff --git a/offload/test/tools/offload-tblgen/print_enum.td b/offload/test/tools/offload-tblgen/print_enum.td
new file mode 100644
index 0000000000000..1e1d7f57218d0
--- /dev/null
+++ b/offload/test/tools/offload-tblgen/print_enum.td
@@ -0,0 +1,34 @@
+// RUN: %offload-tblgen -gen-print-header -I %S/../../../new-api/API %s | %fcheck-generic
+
+// Check that print helpers are created for enums
+
+include "APIDefs.td"
+
+def : Enum {
+  let name = "my_enum_t";
+  let desc = "An example enum";
+  let etors =[
+    Etor<"VALUE_ONE", "The first enum value">,
+    Etor<"VALUE_TWO", "The second enum value">,
+    Etor<"VALUE_THREE", "The third enum value">,
+    Etor<"VALUE_FOUR", "The fourth enum value">,
+  ];
+}
+
+// CHECK: inline std::ostream &operator<<(std::ostream &os, enum my_enum_t value)
+// CHECK: switch (value) {
+// CHECK: case MY_ENUM_VALUE_ONE:
+// CHECK:   os << "MY_ENUM_VALUE_ONE";
+// CHECK:   break;
+// CHECK: case MY_ENUM_VALUE_TWO:
+// CHECK:   os << "MY_ENUM_VALUE_TWO";
+// CHECK:   break;
+// CHECK: case MY_ENUM_VALUE_THREE:
+// CHECK:   os << "MY_ENUM_VALUE_THREE";
+// CHECK:   break;
+// CHECK: case MY_ENUM_VALUE_FOUR:
+// CHECK:   os << "MY_ENUM_VALUE_FOUR";
+// CHECK:   break;
+// CHECK: default:
+// CHECK:   os << "unknown enumerator";
+// CHECK:   break;
diff --git a/offload/test/tools/offload-tblgen/print_function.td b/offload/test/tools/offload-tblgen/print_function.td
new file mode 100644
index 0000000000000..2a9cce724eda9
--- /dev/null
+++ b/offload/test/tools/offload-tblgen/print_function.td
@@ -0,0 +1,38 @@
+// RUN: %offload-tblgen -gen-print-header -I %S/../../../new-api/API %s | %fcheck-generic --check-prefix=CHECK-PRINT
+// RUN: %offload-tblgen -gen-api -I %S/../../../new-api/API %s | %fcheck-generic --check-prefix=CHECK-API
+
+// Check that print helpers are created for functions
+
+include "APIDefs.td"
+
+def : Handle {
+    let name = "offload_foo_handle_t";
+    let desc = "Example handle type";
+}
+
+def : Function {
+    let name = "FunctionA";
+    let desc = "Function A description";
+    let details = [ "Function A detailed information" ];
+    let params = [
+        Param<"uint32_t", "ParamValue", "A plain value parameter">,
+        Param<"offload_foo_handle_t", "ParamHandle", "A handle parameter">,
+        Param<"uint32_t*", "ParamPointer", "A pointer parameter">,
+    ];
+    let returns = [];
+}
+
+// CHECK-API: typedef struct function_a_params_t {
+// CHECK-API-NEXT: uint32_t* pParamValue;
+// CHECK-API-NEXT: offload_foo_handle_t* pParamHandle;
+// CHECK-API-NEXT: uint32_t** pParamPointer;
+
+// CHECK-PRINT: inline std::ostream &operator<<(std::ostream &os, const struct function_a_params_t *params)
+// CHECK-PRINT: os << ".ParamValue = ";
+// CHECK-PRINT: os << *params->pParamValue;
+// CHECK-PRINT: os << ", ";
+// CHECK-PRINT: os << ".ParamHandle = ";
+// CHECK-PRINT: printPtr(os, *params->pParamHandle);
+// CHECK-PRINT: os << ", ";
+// CHECK-PRINT: os << ".ParamPointer = ";
+// CHECK-PRINT: printPtr(os, *params->pParamPointer);
diff --git a/offload/test/tools/offload-tblgen/type_tagged_enum.td b/offload/test/tools/offload-tblgen/type_tagged_enum.td
new file mode 100644
index 0000000000000..ea83545e0a385
--- /dev/null
+++ b/offload/test/tools/offload-tblgen/type_tagged_enum.td
@@ -0,0 +1,76 @@
+// RUN: %offload-tblgen -gen-api -I %S/../../../new-api/API %s | %fcheck-generic --check-prefix=CHECK-API
+// RUN: %offload-tblgen -gen-print-header -I %S/../../../new-api/API %s | %fcheck-generic --check-prefix=CHECK-PRINT
+
+// Check that type-tagged enumerators are implemented correctly. They enable
+// functions to return data of an arbitrary type and size via a void*, using
+// the value of an enum parameter to indicate which type is being returned.
+// This allows, for example, for a single olGetDeviceInfo function, rather
+// than requiring a separate entry point for every possible query.
+
+include "APIDefs.td"
+
+def : Handle {
+    let name = "some_handle_t";
+    let desc = "An example handle type";
+}
+
+def : Enum {
+  let name = "my_type_tagged_enum_t";
+  let desc = "Example type tagged enum";
+  let is_typed = 1;
+  let etors = [
+    TaggedEtor<"VALUE_ONE", "uint32_t", "Value one.">,
+    TaggedEtor<"VALUE_TWO", "char[]", "Value two.">,
+    TaggedEtor<"VALUE_THREE", "some_handle_t", "Value three.">
+  ];
+}
+
+// Check the tagged types appear in the comments
+// CHECK-API: typedef enum my_type_tagged_enum_t {
+// CHECK-API-NEXT: [uint32_t] Value one.
+// CHECK-API-NEXT: MY_TYPE_TAGGED_ENUM_VALUE_ONE = 0,
+// CHECK-API-NEXT: [char[]] Value two.
+// CHECK-API-NEXT: MY_TYPE_TAGGED_ENUM_VALUE_TWO = 1,
+// CHECK-API-NEXT: [some_handle_t] Value three.
+// CHECK-API-NEXT: MY_TYPE_TAGGED_ENUM_VALUE_THREE = 2,
+
+def : Function {
+    let name = "FunctionA";
+    let desc = "Function A description";
+    let details = [ "Function A detailed information" ];
+  let params = [
+    Param<"my_type_tagged_enum_t", "PropName", "type of the info to retrieve", PARAM_IN>,
+    Param<"size_t", "PropSize", "the number of bytes pointed to by PropValue.", PARAM_IN>,
+    TypeTaggedParam<"void*", "PropValue", "array of bytes holding the info. "
+      "If PropSize is not equal to or greater to the real number of bytes needed to return the info "
+      "then the OFFLOAD_ERRC_INVALID_SIZE error is returned and PropValue is not used.", PARAM_OUT,
+      TypeInfo<"PropName" , "PropSize">>
+  ];
+  let returns = [];
+}
+
+// Check that a tagged enum print function definition is generated
+// CHECK-PRINT: void printTagged(std::ostream &os, const void *ptr, my_type_tagged_enum_t value, size_t size) {
+// CHECK-PRINT: case MY_TYPE_TAGGED_ENUM_VALUE_ONE: {
+// CHECK-PRINT:   const uint32_t * const tptr = (const uint32_t * const)ptr;
+// CHECK-PRINT:   os << (const void *)tptr << " (";
+// CHECK-PRINT:   os << *tptr;
+// CHECK-PRINT:   os << ")";
+// CHECK-PRINT:   break;
+// CHECK-PRINT: }
+// CHECK-PRINT: case MY_TYPE_TAGGED_ENUM_VALUE_TWO: {
+// CHECK-PRINT:   printPtr(os, (const char*) ptr);
+// CHECK-PRINT:   break;
+// CHECK-PRINT: }
+// CHECK-PRINT: case MY_TYPE_TAGGED_ENUM_VALUE_THREE: {
+// CHECK-PRINT:   const some_handle_t * const tptr = (const some_handle_t * const)ptr;
+// CHECK-PRINT:   os << (const void *)tptr << " (";
+// CHECK-PRINT:   os << *tptr;
+// CHECK-PRINT:   os << ")";
+// CHECK-PRINT:   break;
+// CHECK-PRINT: }
+
+// Check that the tagged type information is used when printing function parameters
+// CHECK-PRINT: std::ostream &operator<<(std::ostream &os, const struct function_a_params_t *params) {
+// CHECK-PRINT: os << ".PropValue = " 
+// CHECK-PRINT-NEXT: printTagged(os, *params->pPropValue, *params->pPropName, *params->pPropSize);
diff --git a/offload/tools/offload-tblgen/APIGen.cpp b/offload/tools/offload-tblgen/APIGen.cpp
new file mode 100644
index 0000000000000..97a2464f7a75c
--- /dev/null
+++ b/offload/tools/offload-tblgen/APIGen.cpp
@@ -0,0 +1,229 @@
+//===- 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("/// ") +
+           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 << MakeParamComment(Param) << "\n";
+    OS << "  " << Param.getType() << " " << Param.getName();
+    if (Param != Params.back()) {
+      OS << ",\n";
+    } else {
+      OS << "\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()) {
+    if (Enum.isTyped()) {
+      OS << MakeComment(
+          formatv("[{0}] {1}", EnumVal.getTaggedType(), EnumVal.getDesc())
+              .str());
+    } else {
+      OS << MakeComment(EnumVal.getDesc());
+    }
+    OS << formatv(TAB_1 "{0}_{1} = {2},\n", Enum.getEnumValNamePrefix(),
+                  EnumVal.getName(), EtorVal++);
+  }
+
+  // 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) {
+  if (Func.getParams().size() == 0) {
+    return;
+  }
+
+  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());
+}
+
+static void ProcessFuncWithCodeLocVariant(const FunctionRec &Func,
+                                          raw_ostream &OS) {
+
+  auto FuncWithCodeLocBegin = R"(
+///////////////////////////////////////////////////////////////////////////////
+/// @brief Variant of {0} that also sets source code location information
+/// @details See also ::{0}
+OL_APIEXPORT ol_result_t OL_APICALL {0}WithCodeLoc(
+)";
+  OS << formatv(FuncWithCodeLocBegin, Func.getName());
+  auto Params = Func.getParams();
+  for (auto &Param : Params) {
+    OS << "  " << Param.getType() << " " << Param.getName();
+    OS << ",\n";
+  }
+  OS << "ol_code_location_t *CodeLocation);\n\n";
+}
+
+void EmitOffloadAPI(const RecordKeeper &Records, raw_ostream &OS) {
+  OS << GenericHeader;
+  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);
+  }
+
+  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
+    ProcessFuncWithCodeLocVariant(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 0000000000000..52986cbbaa918
--- /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 0000000000000..990ff96a3121d
--- /dev/null
+++ b/offload/tools/offload-tblgen/EntryPointGen.cpp
@@ -0,0 +1,138 @@
+//===- 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 EmitValidationFunc(const FunctionRec &F, raw_ostream &OS) {
+  OS << CommentsHeader;
+  // Emit preamble
+  OS << formatv("{0}_impl_result_t {1}_val(\n  ", PrefixLower, 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 << TAB_1 "}\n\n";
+
+  // Perform actual function call to the implementation
+  ParamNameList = ParamNameList.substr(0, ParamNameList.size() - 2);
+  OS << formatv(TAB_1 "return {0}_impl({1});\n\n", F.getName(), ParamNameList);
+  OS << "}\n";
+}
+
+static void EmitEntryPointFunc(const FunctionRec &F, raw_ostream &OS) {
+  // 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";
+
+  // Emit pre-call prints
+  OS << TAB_1 "if (offloadConfig().TracingEnabled) {\n";
+  OS << formatv(TAB_2 "std::cout << \"---> {0}\";\n", F.getName());
+  OS << TAB_1 "}\n\n";
+
+  // Perform actual function call to the validation wrapper
+  ParamNameList = ParamNameList.substr(0, ParamNameList.size() - 2);
+  OS << formatv(TAB_1 "{0}_result_t Result = {1}_val({2});\n\n", PrefixLower,
+                F.getName(), ParamNameList);
+
+  // Emit post-call prints
+  OS << TAB_1 "if (offloadConfig().TracingEnabled) {\n";
+  if (F.getParams().size() > 0) {
+    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";
+  } else {
+    OS << TAB_2 "std::cout << \"()\";\n";
+  }
+  OS << TAB_2 "std::cout << \"-> \" << Result << \"\\n\";\n";
+  OS << TAB_2 "if (Result && Result->Details) {\n";
+  OS << TAB_3 "std::cout << \"     *Error Details* \" << Result->Details "
+              "<< \" \\n\";\n";
+  OS << TAB_2 "}\n";
+  OS << TAB_1 "}\n";
+
+  OS << TAB_1 "return Result;\n";
+  OS << "}\n";
+}
+
+static void EmitCodeLocWrapper(const FunctionRec &F, raw_ostream &OS) {
+  // Emit preamble
+  OS << formatv("{0}_result_t {1}WithCodeLoc(\n  ", PrefixLower, F.getName());
+  // Emit arguments
+  std::string ParamNameList = "";
+  for (auto &Param : F.getParams()) {
+    OS << Param.getType() << " " << Param.getName() << ", ";
+    ParamNameList += Param.getName().str();
+    if (Param != F.getParams().back()) {
+      ParamNameList += ", ";
+    }
+  }
+  OS << "ol_code_location_t *CodeLocation";
+  OS << ") {\n";
+  OS << TAB_1 "currentCodeLocation() = CodeLocation;\n";
+  OS << formatv(TAB_1 "{0}_result_t Result = {1}({2});\n\n", PrefixLower,
+                F.getName(), ParamNameList);
+  OS << TAB_1 "currentCodeLocation() = nullptr;\n";
+  OS << TAB_1 "return Result;\n";
+  OS << "}\n";
+}
+
+void EmitOffloadEntryPoints(const RecordKeeper &Records, raw_ostream &OS) {
+  OS << GenericHeader;
+  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
+    EmitValidationFunc(FunctionRec{R}, OS);
+    EmitEntryPointFunc(FunctionRec{R}, OS);
+    EmitCodeLocWrapper(FunctionRec{R}, OS);
+  }
+}
diff --git a/offload/tools/offload-tblgen/FuncsGen.cpp b/offload/tools/offload-tblgen/FuncsGen.cpp
new file mode 100644
index 0000000000000..3238652176198
--- /dev/null
+++ b/offload/tools/offload-tblgen/FuncsGen.cpp
@@ -0,0 +1,74 @@
+//===- offload-tblgen/APIGen.cpp - Tablegen backend for Offload functions -===//
+//
+// 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 handles generation of various small files
+// pertaining to the API 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(const RecordKeeper &Records, raw_ostream &OS) {
+  OS << GenericHeader;
+  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";
+  }
+  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
+    FunctionRec FR{R};
+    OS << formatv("OFFLOAD_FUNC({0}WithCodeLoc)", FR.getName()) << "\n";
+  }
+
+  OS << "\n#undef OFFLOAD_FUNC\n";
+}
+
+void EmitOffloadExports(const RecordKeeper &Records, raw_ostream &OS) {
+  OS << "VERS1.0 {\n";
+  OS << TAB_1 "global:\n";
+
+  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
+    OS << formatv(TAB_2 "{0};\n", FunctionRec(R).getName());
+  }
+  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
+    OS << formatv(TAB_2 "{0}WithCodeLoc;\n", FunctionRec(R).getName());
+  }
+  OS << TAB_1 "local:\n";
+  OS << TAB_2 "*;\n";
+  OS << "};\n";
+}
+
+// Emit declarations for every implementation function
+void EmitOffloadImplFuncDecls(const RecordKeeper &Records, raw_ostream &OS) {
+  OS << GenericHeader;
+  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
+    FunctionRec F{R};
+    OS << formatv("{0}_impl_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 << ", ";
+      }
+    }
+    OS << ");\n\n";
+  }
+}
diff --git a/offload/tools/offload-tblgen/GenCommon.hpp b/offload/tools/offload-tblgen/GenCommon.hpp
new file mode 100644
index 0000000000000..db432e9958b5d
--- /dev/null
+++ b/offload/tools/offload-tblgen/GenCommon.hpp
@@ -0,0 +1,67 @@
+//===- 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 GenericHeader =
+    R"(//===- Auto-generated file, part of the LLVM/Offload project --------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+)";
+
+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 = "ol";
+constexpr auto PrefixUpper = "OL";
+
+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 0000000000000..8b6104c5cd9c6
--- /dev/null
+++ b/offload/tools/offload-tblgen/Generators.hpp
@@ -0,0 +1,23 @@
+//===- 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(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS);
+void EmitOffloadFuncNames(const llvm::RecordKeeper &Records,
+                          llvm::raw_ostream &OS);
+void EmitOffloadImplFuncDecls(const llvm::RecordKeeper &Records,
+                              llvm::raw_ostream &OS);
+void EmitOffloadEntryPoints(const llvm::RecordKeeper &Records,
+                            llvm::raw_ostream &OS);
+void EmitOffloadPrintHeader(const llvm::RecordKeeper &Records,
+                            llvm::raw_ostream &OS);
+void EmitOffloadExports(const 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 0000000000000..2a7c63c3dfd1f
--- /dev/null
+++ b/offload/tools/offload-tblgen/PrintGen.cpp
@@ -0,0 +1,226 @@
+//===- 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 EmitResultPrint(raw_ostream &OS) {
+  OS << R""(
+inline std::ostream &operator<<(std::ostream &os,
+                                const ol_error_struct_t *Err) {
+  if (Err == nullptr) {
+    os << "OL_SUCCESS";
+  } else {
+    os << Err->Code;
+  }
+  return os;
+}
+)"";
+}
+
+static void EmitFunctionParamStructPrint(const FunctionRec &Func,
+                                         raw_ostream &OS) {
+  if (Func.getParams().size() == 0) {
+    return;
+  }
+
+  OS << formatv(R"(
+inline std::ostream &operator<<(std::ostream &os, 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(const RecordKeeper &Records, raw_ostream &OS) {
+  OS << GenericHeader;
+  OS << R"""(
+// Auto-generated file, do not manually edit.
+
+#pragma once
+
+#include <OffloadAPI.h>
+#include <ostream>
+
+
+template <typename T> inline ol_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);
+  }
+  EmitResultPrint(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 ol_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 OL_SUCCESS;
+}
+  )""";
+}
diff --git a/offload/tools/offload-tblgen/RecordTypes.hpp b/offload/tools/offload-tblgen/RecordTypes.hpp
new file mode 100644
index 0000000000000..0bf3256c525d9
--- /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(const Record *rec) : rec(rec) {}
+  StringRef getName() const { return rec->getValueAsString("name"); }
+  StringRef getDesc() const { return rec->getValueAsString("desc"); }
+
+private:
+  const Record *rec;
+};
+
+class MacroRec {
+public:
+  explicit MacroRec(const 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:
+  const Record *rec;
+  std::string nameWithoutArgs;
+};
+
+class TypedefRec {
+public:
+  explicit TypedefRec(const 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:
+  const Record *rec;
+};
+
+class EnumValueRec {
+public:
+  explicit EnumValueRec(const 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:
+  const Record *rec;
+};
+
+class EnumRec {
+public:
+  explicit EnumRec(const Record *rec) : rec(rec) {
+    for (const 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:
+  const Record *rec;
+  std::vector<EnumValueRec> vals;
+};
+
+class StructMemberRec {
+public:
+  explicit StructMemberRec(const 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:
+  const Record *rec;
+};
+
+class StructRec {
+public:
+  explicit StructRec(const 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:
+  const Record *rec;
+  std::vector<StructMemberRec> members;
+};
+
+class ParamRec {
+public:
+  explicit ParamRec(const 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(); }
+
+  const 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:
+  const Record *rec;
+  const BitsInit *flags;
+  std::optional<std::pair<StringRef, StringRef>> range;
+  std::optional<std::pair<StringRef, StringRef>> typeinfo;
+};
+
+class ReturnRec {
+public:
+  ReturnRec(const Record *rec) : rec(rec) {}
+  StringRef getValue() const { return rec->getValueAsString("value"); }
+  std::vector<StringRef> getConditions() const {
+    return rec->getValueAsListOfStrings("conditions");
+  }
+
+private:
+  const Record *rec;
+};
+
+class FunctionRec {
+public:
+  FunctionRec(const 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;
+
+  const 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 0000000000000..1912abf5265c7
--- /dev/null
+++ b/offload/tools/offload-tblgen/offload-tblgen.cpp
@@ -0,0 +1,101 @@
+//===- 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,
+  GenExports
+};
+
+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"),
+        clEnumValN(GenExports, "gen-exports",
+                   "Generate export file for the Offload library")));
+}
+
+static bool OffloadTableGenMain(raw_ostream &OS, const 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;
+  case GenExports:
+    EmitOffloadExports(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); }
diff --git a/offload/unittests/CMakeLists.txt b/offload/unittests/CMakeLists.txt
index 73c87b708d25f..25ac4b2fa3675 100644
--- a/offload/unittests/CMakeLists.txt
+++ b/offload/unittests/CMakeLists.txt
@@ -5,4 +5,5 @@ function(add_libompt_unittest test_dirname)
   add_unittest(LibomptUnitTests ${test_dirname} ${ARGN})
 endfunction()
 
-add_subdirectory(Plugins)
+# add_subdirectory(Plugins)
+add_subdirectory(OffloadAPI)
diff --git a/offload/unittests/OffloadAPI/CMakeLists.txt b/offload/unittests/OffloadAPI/CMakeLists.txt
new file mode 100644
index 0000000000000..033ee2b6ec746
--- /dev/null
+++ b/offload/unittests/OffloadAPI/CMakeLists.txt
@@ -0,0 +1,16 @@
+set(PLUGINS_TEST_COMMON LLVMOffload)
+set(PLUGINS_TEST_INCLUDE ${LIBOMPTARGET_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/common)
+
+add_libompt_unittest("offload.unittests"
+    ${CMAKE_CURRENT_SOURCE_DIR}/common/Environment.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/platform/olGetPlatform.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/platform/olGetPlatformCount.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/platform/olGetPlatformInfo.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/platform/olGetPlatformInfoSize.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/device/olGetDevice.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/device/olGetDeviceCount.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/device/olGetDeviceInfo.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/device/olGetDeviceInfoSize.cpp)
+add_dependencies("offload.unittests" ${PLUGINS_TEST_COMMON})
+target_link_libraries("offload.unittests" PRIVATE ${PLUGINS_TEST_COMMON})
+target_include_directories("offload.unittests" PRIVATE ${PLUGINS_TEST_INCLUDE})
diff --git a/offload/unittests/OffloadAPI/common/Environment.cpp b/offload/unittests/OffloadAPI/common/Environment.cpp
new file mode 100644
index 0000000000000..f07a66cda2189
--- /dev/null
+++ b/offload/unittests/OffloadAPI/common/Environment.cpp
@@ -0,0 +1,96 @@
+//===------- Offload API tests - gtest environment ------------------------===//
+//
+// 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 "Environment.hpp"
+#include "Fixtures.hpp"
+#include "llvm/Support/CommandLine.h"
+#include <OffloadAPI.h>
+
+using namespace llvm;
+
+// Wrapper so we don't have to constantly init and shutdown Offload in every
+// test, while having sensible lifetime for the platform environment
+struct OffloadInitWrapper {
+  OffloadInitWrapper() { olInit(); }
+  ~OffloadInitWrapper() { olShutDown(); }
+};
+static OffloadInitWrapper Wrapper{};
+
+static cl::opt<std::string>
+    SelectedPlatform("platform", cl::desc("Only test the specified platform"),
+                     cl::value_desc("platform"));
+
+std::ostream &operator<<(std::ostream &Out,
+                         const ol_platform_handle_t &Platform) {
+  size_t Size;
+  olGetPlatformInfoSize(Platform, OL_PLATFORM_INFO_NAME, &Size);
+  std::vector<char> Name(Size);
+  olGetPlatformInfo(Platform, OL_PLATFORM_INFO_NAME, Size, Name.data());
+  Out << Name.data();
+  return Out;
+}
+
+std::ostream &operator<<(std::ostream &Out,
+                         const std::vector<ol_platform_handle_t> &Platforms) {
+  for (auto Platform : Platforms) {
+    Out << "\n  * \"" << Platform << "\"";
+  }
+  return Out;
+}
+
+const std::vector<ol_platform_handle_t> &TestEnvironment::getPlatforms() {
+  static std::vector<ol_platform_handle_t> Platforms{};
+
+  if (Platforms.empty()) {
+    uint32_t PlatformCount = 0;
+    olGetPlatformCount(&PlatformCount);
+    if (PlatformCount > 0) {
+      Platforms.resize(PlatformCount);
+      olGetPlatform(PlatformCount, Platforms.data());
+    }
+  }
+
+  return Platforms;
+}
+
+// Get a single platform, which may be selected by the user.
+ol_platform_handle_t TestEnvironment::getPlatform() {
+  static ol_platform_handle_t Platform = nullptr;
+  const auto &Platforms = getPlatforms();
+
+  if (!Platform) {
+    if (SelectedPlatform != "") {
+      for (const auto CandidatePlatform : Platforms) {
+        std::stringstream PlatformName;
+        PlatformName << CandidatePlatform;
+        if (SelectedPlatform == PlatformName.str()) {
+          Platform = CandidatePlatform;
+          return Platform;
+        }
+      }
+      std::cout << "No platform found with the name \"" << SelectedPlatform
+                << "\". Choose from:" << Platforms << "\n";
+      std::exit(1);
+    } else {
+      // Pick a single platform. We prefer one that has available devices, but
+      // just pick the first initially in case none have any devices.
+      Platform = Platforms[0];
+      for (auto CandidatePlatform : Platforms) {
+        uint32_t NumDevices = 0;
+        if (olGetDeviceCount(CandidatePlatform, &NumDevices) == OL_SUCCESS) {
+          if (NumDevices > 0) {
+            Platform = CandidatePlatform;
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  return Platform;
+}
diff --git a/offload/unittests/OffloadAPI/common/Environment.hpp b/offload/unittests/OffloadAPI/common/Environment.hpp
new file mode 100644
index 0000000000000..6dba2381eb0b7
--- /dev/null
+++ b/offload/unittests/OffloadAPI/common/Environment.hpp
@@ -0,0 +1,17 @@
+//===------- Offload API tests - gtest environment ------------------------===//
+//
+// 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 <OffloadAPI.h>
+#include <gtest/gtest.h>
+
+namespace TestEnvironment {
+const std::vector<ol_platform_handle_t> &getPlatforms();
+ol_platform_handle_t getPlatform();
+} // namespace TestEnvironment
diff --git a/offload/unittests/OffloadAPI/common/Fixtures.hpp b/offload/unittests/OffloadAPI/common/Fixtures.hpp
new file mode 100644
index 0000000000000..410a435dee1b5
--- /dev/null
+++ b/offload/unittests/OffloadAPI/common/Fixtures.hpp
@@ -0,0 +1,64 @@
+//===------- Offload API tests - gtest fixtures --==-----------------------===//
+//
+// 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 <OffloadAPI.h>
+#include <OffloadPrint.hpp>
+#include <gtest/gtest.h>
+
+#include "Environment.hpp"
+
+#pragma once
+
+#ifndef ASSERT_SUCCESS
+#define ASSERT_SUCCESS(ACTUAL) ASSERT_EQ(OL_SUCCESS, ACTUAL)
+#endif
+
+// TODO: rework this so the EXPECTED/ACTUAL results are readable
+#ifndef ASSERT_ERROR
+#define ASSERT_ERROR(EXPECTED, ACTUAL)                                         \
+  do {                                                                         \
+    ol_result_t Res = ACTUAL;                                                  \
+    ASSERT_TRUE(Res && (Res->Code == EXPECTED));                               \
+  } while (0)
+#endif
+
+#define RETURN_ON_FATAL_FAILURE(...)                                           \
+  __VA_ARGS__;                                                                 \
+  if (this->HasFatalFailure() || this->IsSkipped()) {                          \
+    return;                                                                    \
+  }                                                                            \
+  (void)0
+
+struct offloadTest : ::testing::Test {
+  // No special behavior now, but just in case we need to override it in future
+};
+
+struct offloadPlatformTest : offloadTest {
+  void SetUp() override {
+    RETURN_ON_FATAL_FAILURE(offloadTest::SetUp());
+
+    Platform = TestEnvironment::getPlatform();
+    ASSERT_NE(Platform, nullptr);
+  }
+
+  ol_platform_handle_t Platform;
+};
+
+struct offloadDeviceTest : offloadPlatformTest {
+  void SetUp() override {
+    RETURN_ON_FATAL_FAILURE(offloadPlatformTest::SetUp());
+
+    uint32_t NumDevices;
+    ASSERT_SUCCESS(olGetDeviceCount(Platform, &NumDevices));
+    if (NumDevices == 0)
+      GTEST_SKIP() << "No available devices on this platform.";
+    ASSERT_SUCCESS(olGetDevice(Platform, 1, &Device));
+  }
+
+  ol_device_handle_t Device;
+};
diff --git a/offload/unittests/OffloadAPI/device/olDeviceInfo.hpp b/offload/unittests/OffloadAPI/device/olDeviceInfo.hpp
new file mode 100644
index 0000000000000..06915258da384
--- /dev/null
+++ b/offload/unittests/OffloadAPI/device/olDeviceInfo.hpp
@@ -0,0 +1,21 @@
+//===------- Offload API tests - Helpers for device info query testing ----===//
+//
+// 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 <unordered_map>
+#include <vector>
+
+// TODO: We could autogenerate these
+inline std::vector<ol_device_info_t> DeviceQueries = {
+    OL_DEVICE_INFO_TYPE, OL_DEVICE_INFO_PLATFORM, OL_DEVICE_INFO_NAME,
+    OL_DEVICE_INFO_VENDOR, OL_DEVICE_INFO_DRIVER_VERSION};
+
+inline std::unordered_map<ol_device_info_t, size_t> DeviceInfoSizeMap = {
+    {OL_DEVICE_INFO_TYPE, sizeof(ol_device_type_t)},
+    {OL_DEVICE_INFO_PLATFORM, sizeof(ol_platform_handle_t)},
+};
diff --git a/offload/unittests/OffloadAPI/device/olGetDevice.cpp b/offload/unittests/OffloadAPI/device/olGetDevice.cpp
new file mode 100644
index 0000000000000..68d4682dd3351
--- /dev/null
+++ b/offload/unittests/OffloadAPI/device/olGetDevice.cpp
@@ -0,0 +1,39 @@
+//===------- Offload API tests - olGetDevice -------------------------===//
+//
+// 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 "../common/Fixtures.hpp"
+#include <OffloadAPI.h>
+#include <gtest/gtest.h>
+
+using olGetDeviceTest = offloadPlatformTest;
+
+TEST_F(olGetDeviceTest, Success) {
+  uint32_t Count = 0;
+  ASSERT_SUCCESS(olGetDeviceCount(Platform, &Count));
+  if (Count == 0)
+    GTEST_SKIP() << "No available devices on this platform.";
+
+  std::vector<ol_device_handle_t> Devices(Count);
+  ASSERT_SUCCESS(olGetDevice(Platform, Count, Devices.data()));
+  for (auto Device : Devices) {
+    ASSERT_NE(nullptr, Device);
+  }
+}
+
+TEST_F(olGetDeviceTest, SuccessSubsetOfDevices) {
+  uint32_t Count;
+  ASSERT_SUCCESS(olGetDeviceCount(Platform, &Count));
+  if (Count < 2)
+    GTEST_SKIP() << "Only one device is available on this platform.";
+
+  std::vector<ol_device_handle_t> Devices(Count - 1);
+  ASSERT_SUCCESS(olGetDevice(Platform, Count - 1, Devices.data()));
+  for (auto Device : Devices) {
+    ASSERT_NE(nullptr, Device);
+  }
+}
diff --git a/offload/unittests/OffloadAPI/device/olGetDeviceCount.cpp b/offload/unittests/OffloadAPI/device/olGetDeviceCount.cpp
new file mode 100644
index 0000000000000..ef377d671bf60
--- /dev/null
+++ b/offload/unittests/OffloadAPI/device/olGetDeviceCount.cpp
@@ -0,0 +1,28 @@
+//===------- Offload API tests - olGetDeviceCount --------------------===//
+//
+// 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 "../common/Fixtures.hpp"
+#include <OffloadAPI.h>
+#include <gtest/gtest.h>
+
+using olGetDeviceCountTest = offloadPlatformTest;
+
+TEST_F(olGetDeviceCountTest, Success) {
+  uint32_t Count = 0;
+  ASSERT_SUCCESS(olGetDeviceCount(Platform, &Count));
+}
+
+TEST_F(olGetDeviceCountTest, InvalidNullPlatform) {
+  uint32_t Count = 0;
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_HANDLE, olGetDeviceCount(nullptr, &Count));
+}
+
+TEST_F(olGetDeviceCountTest, InvalidNullPointer) {
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_POINTER,
+               olGetDeviceCount(Platform, nullptr));
+}
diff --git a/offload/unittests/OffloadAPI/device/olGetDeviceInfo.cpp b/offload/unittests/OffloadAPI/device/olGetDeviceInfo.cpp
new file mode 100644
index 0000000000000..c936802fb1e4d
--- /dev/null
+++ b/offload/unittests/OffloadAPI/device/olGetDeviceInfo.cpp
@@ -0,0 +1,76 @@
+//===------- Offload API tests - olGetDeviceInfo ---------------------===//
+//
+// 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 "../common/Fixtures.hpp"
+#include "olDeviceInfo.hpp"
+#include <OffloadAPI.h>
+#include <gtest/gtest.h>
+
+struct olGetDeviceInfoTest : offloadDeviceTest,
+                             ::testing::WithParamInterface<ol_device_info_t> {
+
+  void SetUp() override { RETURN_ON_FATAL_FAILURE(offloadDeviceTest::SetUp()); }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    , olGetDeviceInfoTest, ::testing::ValuesIn(DeviceQueries),
+    [](const ::testing::TestParamInfo<ol_device_info_t> &info) {
+      std::stringstream ss;
+      ss << info.param;
+      return ss.str();
+    });
+
+TEST_P(olGetDeviceInfoTest, Success) {
+  ol_device_info_t InfoType = GetParam();
+  size_t Size = 0;
+
+  ASSERT_SUCCESS(olGetDeviceInfoSize(Device, InfoType, &Size));
+
+  std::vector<char> InfoData(Size);
+  ASSERT_SUCCESS(olGetDeviceInfo(Device, InfoType, Size, InfoData.data()));
+
+  if (InfoType == OL_DEVICE_INFO_PLATFORM) {
+    auto *ReturnedPlatform =
+        reinterpret_cast<ol_platform_handle_t *>(InfoData.data());
+    ASSERT_EQ(Platform, *ReturnedPlatform);
+  }
+}
+
+TEST_F(olGetDeviceInfoTest, InvalidNullHandleDevice) {
+  ol_device_type_t DeviceType;
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_HANDLE,
+               olGetDeviceInfo(nullptr, OL_DEVICE_INFO_TYPE,
+                               sizeof(ol_device_type_t), &DeviceType));
+}
+
+TEST_F(olGetDeviceInfoTest, InvalidEnumerationInfoType) {
+  ol_device_type_t DeviceType;
+  ASSERT_ERROR(OL_ERRC_INVALID_ENUMERATION,
+               olGetDeviceInfo(Device, OL_DEVICE_INFO_FORCE_UINT32,
+                               sizeof(ol_device_type_t), &DeviceType));
+}
+
+TEST_F(olGetDeviceInfoTest, InvalidSizePropSize) {
+  ol_device_type_t DeviceType;
+  ASSERT_ERROR(OL_ERRC_INVALID_SIZE,
+               olGetDeviceInfo(Device, OL_DEVICE_INFO_TYPE, 0, &DeviceType));
+}
+
+TEST_F(olGetDeviceInfoTest, InvalidSizePropSizeSmall) {
+  ol_device_type_t DeviceType;
+  ASSERT_ERROR(OL_ERRC_INVALID_SIZE,
+               olGetDeviceInfo(Device, OL_DEVICE_INFO_TYPE,
+                               sizeof(DeviceType) - 1, &DeviceType));
+}
+
+TEST_F(olGetDeviceInfoTest, InvalidNullPointerPropValue) {
+  ol_device_type_t DeviceType;
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_POINTER,
+               olGetDeviceInfo(Device, OL_DEVICE_INFO_TYPE, sizeof(DeviceType),
+                               nullptr));
+}
diff --git a/offload/unittests/OffloadAPI/device/olGetDeviceInfoSize.cpp b/offload/unittests/OffloadAPI/device/olGetDeviceInfoSize.cpp
new file mode 100644
index 0000000000000..9e792d1c3e25e
--- /dev/null
+++ b/offload/unittests/OffloadAPI/device/olGetDeviceInfoSize.cpp
@@ -0,0 +1,58 @@
+//===------- Offload API tests - olGetDeviceInfoSize -----------------===//
+//
+// 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 <OffloadAPI.h>
+
+#include "../common/Fixtures.hpp"
+#include "olDeviceInfo.hpp"
+
+struct olGetDeviceInfoSizeTest
+    : offloadDeviceTest,
+      ::testing::WithParamInterface<ol_device_info_t> {
+
+  void SetUp() override { RETURN_ON_FATAL_FAILURE(offloadDeviceTest::SetUp()); }
+};
+
+// TODO: We could autogenerate the list of enum values
+INSTANTIATE_TEST_SUITE_P(
+    , olGetDeviceInfoSizeTest, ::testing::ValuesIn(DeviceQueries),
+    [](const ::testing::TestParamInfo<ol_device_info_t> &info) {
+      std::stringstream ss;
+      ss << info.param;
+      return ss.str();
+    });
+
+TEST_P(olGetDeviceInfoSizeTest, Success) {
+  ol_device_info_t InfoType = GetParam();
+  size_t Size = 0;
+
+  ASSERT_SUCCESS(olGetDeviceInfoSize(Device, InfoType, &Size));
+  auto ExpectedSize = DeviceInfoSizeMap.find(InfoType);
+  if (ExpectedSize != DeviceInfoSizeMap.end()) {
+    ASSERT_EQ(Size, ExpectedSize->second);
+  } else {
+    ASSERT_NE(Size, 0lu);
+  }
+}
+
+TEST_F(olGetDeviceInfoSizeTest, InvalidNullHandle) {
+  size_t Size = 0;
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_HANDLE,
+               olGetDeviceInfoSize(nullptr, OL_DEVICE_INFO_TYPE, &Size));
+}
+
+TEST_F(olGetDeviceInfoSizeTest, InvalidDeviceInfoEnumeration) {
+  size_t Size = 0;
+  ASSERT_ERROR(OL_ERRC_INVALID_ENUMERATION,
+               olGetDeviceInfoSize(Device, OL_DEVICE_INFO_FORCE_UINT32, &Size));
+}
+
+TEST_F(olGetDeviceInfoSizeTest, InvalidNullPointer) {
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_POINTER,
+               olGetDeviceInfoSize(Device, OL_DEVICE_INFO_TYPE, nullptr));
+}
diff --git a/offload/unittests/OffloadAPI/platform/olGetPlatform.cpp b/offload/unittests/OffloadAPI/platform/olGetPlatform.cpp
new file mode 100644
index 0000000000000..4a2f9e8ac7741
--- /dev/null
+++ b/offload/unittests/OffloadAPI/platform/olGetPlatform.cpp
@@ -0,0 +1,28 @@
+//===------- Offload API tests - olGetPlatform -----------------------===//
+//
+// 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 "../common/Fixtures.hpp"
+#include <OffloadAPI.h>
+#include <gtest/gtest.h>
+
+using olGetPlatformTest = offloadTest;
+
+TEST_F(olGetPlatformTest, Success) {
+  uint32_t PlatformCount;
+  ASSERT_SUCCESS(olGetPlatformCount(&PlatformCount));
+  std::vector<ol_platform_handle_t> Platforms(PlatformCount);
+  ASSERT_SUCCESS(olGetPlatform(PlatformCount, Platforms.data()));
+}
+
+TEST_F(olGetPlatformTest, InvalidNumEntries) {
+  uint32_t PlatformCount;
+  ASSERT_SUCCESS(olGetPlatformCount(&PlatformCount));
+  std::vector<ol_platform_handle_t> Platforms(PlatformCount);
+  ASSERT_ERROR(OL_ERRC_INVALID_SIZE,
+               olGetPlatform(PlatformCount + 1, Platforms.data()));
+}
diff --git a/offload/unittests/OffloadAPI/platform/olGetPlatformCount.cpp b/offload/unittests/OffloadAPI/platform/olGetPlatformCount.cpp
new file mode 100644
index 0000000000000..15b4b6abcd70d
--- /dev/null
+++ b/offload/unittests/OffloadAPI/platform/olGetPlatformCount.cpp
@@ -0,0 +1,22 @@
+//===------- Offload API tests - olGetPlatformCount ------------------===//
+//
+// 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 "../common/Fixtures.hpp"
+#include <OffloadAPI.h>
+#include <gtest/gtest.h>
+
+using olGetPlatformCountTest = offloadTest;
+
+TEST_F(olGetPlatformCountTest, Success) {
+  uint32_t PlatformCount;
+  ASSERT_SUCCESS(olGetPlatformCount(&PlatformCount));
+}
+
+TEST_F(olGetPlatformCountTest, InvalidNullPointer) {
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_POINTER, olGetPlatformCount(nullptr));
+}
diff --git a/offload/unittests/OffloadAPI/platform/olGetPlatformInfo.cpp b/offload/unittests/OffloadAPI/platform/olGetPlatformInfo.cpp
new file mode 100644
index 0000000000000..c646bdc50b7da
--- /dev/null
+++ b/offload/unittests/OffloadAPI/platform/olGetPlatformInfo.cpp
@@ -0,0 +1,76 @@
+//===------- Offload API tests - olGetPlatformInfo -------------------===//
+//
+// 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 <OffloadAPI.h>
+
+#include "../common/Fixtures.hpp"
+#include "olPlatformInfo.hpp"
+
+struct olGetPlatformInfoTest
+    : offloadPlatformTest,
+      ::testing::WithParamInterface<ol_platform_info_t> {};
+
+INSTANTIATE_TEST_SUITE_P(
+    olGetPlatformInfo, olGetPlatformInfoTest,
+    ::testing::ValuesIn(PlatformQueries),
+    [](const ::testing::TestParamInfo<ol_platform_info_t> &info) {
+      std::stringstream ss;
+      ss << info.param;
+      return ss.str();
+    });
+
+TEST_P(olGetPlatformInfoTest, Success) {
+  size_t Size = 0;
+  ol_platform_info_t InfoType = GetParam();
+
+  ASSERT_SUCCESS(olGetPlatformInfoSize(Platform, InfoType, &Size));
+  std::vector<char> InfoData(Size);
+  ASSERT_SUCCESS(olGetPlatformInfo(Platform, InfoType, Size, InfoData.data()));
+
+  // Info types with a dynamic size are all char[] so we can verify the returned
+  // string is the expected size.
+  auto ExpectedSize = PlatformInfoSizeMap.find(InfoType);
+  if (ExpectedSize == PlatformInfoSizeMap.end()) {
+    ASSERT_EQ(Size, strlen(InfoData.data()) + 1);
+  }
+}
+
+TEST_F(olGetPlatformInfoTest, InvalidNullHandle) {
+  ol_platform_backend_t Backend;
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_HANDLE,
+               olGetPlatformInfo(nullptr, OL_PLATFORM_INFO_BACKEND,
+                                 sizeof(Backend), &Backend));
+}
+
+TEST_F(olGetPlatformInfoTest, InvalidPlatformInfoEnumeration) {
+  ol_platform_backend_t Backend;
+  ASSERT_ERROR(OL_ERRC_INVALID_ENUMERATION,
+               olGetPlatformInfo(Platform, OL_PLATFORM_INFO_FORCE_UINT32,
+                                 sizeof(Backend), &Backend));
+}
+
+TEST_F(olGetPlatformInfoTest, InvalidSizeZero) {
+  ol_platform_backend_t Backend;
+  ASSERT_ERROR(
+      OL_ERRC_INVALID_SIZE,
+      olGetPlatformInfo(Platform, OL_PLATFORM_INFO_BACKEND, 0, &Backend));
+}
+
+TEST_F(olGetPlatformInfoTest, InvalidSizeSmall) {
+  ol_platform_backend_t Backend;
+  ASSERT_ERROR(OL_ERRC_INVALID_SIZE,
+               olGetPlatformInfo(Platform, OL_PLATFORM_INFO_BACKEND,
+                                 sizeof(Backend) - 1, &Backend));
+}
+
+TEST_F(olGetPlatformInfoTest, InvalidNullPointerPropValue) {
+  ol_platform_backend_t Backend;
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_POINTER,
+               olGetPlatformInfo(Platform, OL_PLATFORM_INFO_BACKEND,
+                                 sizeof(Backend), nullptr));
+}
diff --git a/offload/unittests/OffloadAPI/platform/olGetPlatformInfoSize.cpp b/offload/unittests/OffloadAPI/platform/olGetPlatformInfoSize.cpp
new file mode 100644
index 0000000000000..7c9274082e8e4
--- /dev/null
+++ b/offload/unittests/OffloadAPI/platform/olGetPlatformInfoSize.cpp
@@ -0,0 +1,57 @@
+//===------- Offload API tests - olGetPlatformInfoSize ---------------===//
+//
+// 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 <OffloadAPI.h>
+
+#include "../common/Fixtures.hpp"
+#include "olPlatformInfo.hpp"
+
+struct olGetPlatformInfoSizeTest
+    : offloadPlatformTest,
+      ::testing::WithParamInterface<ol_platform_info_t> {};
+
+INSTANTIATE_TEST_SUITE_P(
+    olGetPlatformInfoSize, olGetPlatformInfoSizeTest,
+    ::testing::ValuesIn(PlatformQueries),
+    [](const ::testing::TestParamInfo<ol_platform_info_t> &info) {
+      std::stringstream ss;
+      ss << info.param;
+      return ss.str();
+    });
+
+TEST_P(olGetPlatformInfoSizeTest, Success) {
+  size_t Size = 0;
+  ol_platform_info_t InfoType = GetParam();
+
+  ASSERT_SUCCESS(olGetPlatformInfoSize(Platform, InfoType, &Size));
+  auto ExpectedSize = PlatformInfoSizeMap.find(InfoType);
+  if (ExpectedSize != PlatformInfoSizeMap.end()) {
+    ASSERT_EQ(Size, ExpectedSize->second);
+  } else {
+    ASSERT_NE(Size, 0lu);
+  }
+}
+
+TEST_F(olGetPlatformInfoSizeTest, InvalidNullHandle) {
+  size_t Size = 0;
+  ASSERT_ERROR(OL_ERRC_INVALID_NULL_HANDLE,
+               olGetPlatformInfoSize(nullptr, OL_PLATFORM_INFO_BACKEND, &Size));
+}
+
+TEST_F(olGetPlatformInfoSizeTest, InvalidPlatformInfoEnumeration) {
+  size_t Size = 0;
+  ASSERT_ERROR(
+      OL_ERRC_INVALID_ENUMERATION,
+      olGetPlatformInfoSize(Platform, OL_PLATFORM_INFO_FORCE_UINT32, &Size));
+}
+
+TEST_F(olGetPlatformInfoSizeTest, InvalidNullPointer) {
+  ASSERT_ERROR(
+      OL_ERRC_INVALID_NULL_POINTER,
+      olGetPlatformInfoSize(Platform, OL_PLATFORM_INFO_BACKEND, nullptr));
+}
diff --git a/offload/unittests/OffloadAPI/platform/olPlatformInfo.hpp b/offload/unittests/OffloadAPI/platform/olPlatformInfo.hpp
new file mode 100644
index 0000000000000..d49cdb90d321a
--- /dev/null
+++ b/offload/unittests/OffloadAPI/platform/olPlatformInfo.hpp
@@ -0,0 +1,20 @@
+//===------- Offload API tests - Helpers for platform info query testing --===//
+//
+// 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 <vector>
+
+// TODO: We could autogenerate these
+
+inline std::vector<ol_platform_info_t> PlatformQueries = {
+    OL_PLATFORM_INFO_NAME, OL_PLATFORM_INFO_VENDOR_NAME,
+    OL_PLATFORM_INFO_VERSION, OL_PLATFORM_INFO_BACKEND};
+
+inline std::unordered_map<ol_platform_info_t, size_t> PlatformInfoSizeMap = {
+    {OL_PLATFORM_INFO_BACKEND, sizeof(ol_platform_backend_t)},
+};