Skip to content

Conversation

@aelovikov-intel
Copy link
Contributor

Various offload APIs olGet*Info are essentially untyped because they "return" value via void *PropValue output parameter. However, for C++ consumers (e.g., SYCL in #166927) it would be beneficial if we could recover that type information. Before this PR it was only encoded in the comments near corresponding information info descriptors, e.g.,

///////////////////////////////////////////////////////////////////////////////
/// @brief Supported event info.
typedef enum ol_event_info_t {
  /// [ol_queue_handle_t] The handle of the queue associated with the device.
  OL_EVENT_INFO_QUEUE = 0,
  /// [bool] True if and only if the event is complete.
  OL_EVENT_INFO_IS_COMPLETE = 1,
  /// @cond
  OL_EVENT_INFO_LAST = 2,
  OL_EVENT_INFO_FORCE_UINT32 = 0x7fffffff
  /// @endcond

} ol_event_info_t;

and not accessible programmatically.

Various offload APIs `olGet*Info` are essentially untyped because they
"return" value via `void *PropValue` output parameter. However, for C++
consumers (e.g., SYCL in llvm#166927) it would be beneficial if we could recover
that type information. Before this PR it was only encoded in the
comments near corresponding information info descriptors, e.g.,

```c++
///////////////////////////////////////////////////////////////////////////////
/// @brief Supported event info.
typedef enum ol_event_info_t {
  /// [ol_queue_handle_t] The handle of the queue associated with the device.
  OL_EVENT_INFO_QUEUE = 0,
  /// [bool] True if and only if the event is complete.
  OL_EVENT_INFO_IS_COMPLETE = 1,
  /// @cond
  OL_EVENT_INFO_LAST = 2,
  OL_EVENT_INFO_FORCE_UINT32 = 0x7fffffff
  /// @endcond

} ol_event_info_t;
```

and not accessible programmatically.
@llvmbot
Copy link
Member

llvmbot commented Nov 18, 2025

@llvm/pr-subscribers-offload

Author: Andrei Elovikov (aelovikov-intel)

Changes

Various offload APIs olGet*Info are essentially untyped because they "return" value via void *PropValue output parameter. However, for C++ consumers (e.g., SYCL in #166927) it would be beneficial if we could recover that type information. Before this PR it was only encoded in the comments near corresponding information info descriptors, e.g.,

///////////////////////////////////////////////////////////////////////////////
/// @<!-- -->brief Supported event info.
typedef enum ol_event_info_t {
  /// [ol_queue_handle_t] The handle of the queue associated with the device.
  OL_EVENT_INFO_QUEUE = 0,
  /// [bool] True if and only if the event is complete.
  OL_EVENT_INFO_IS_COMPLETE = 1,
  /// @<!-- -->cond
  OL_EVENT_INFO_LAST = 2,
  OL_EVENT_INFO_FORCE_UINT32 = 0x7fffffff
  /// @<!-- -->endcond

} ol_event_info_t;

and not accessible programmatically.


Full diff: https://github.com/llvm/llvm-project/pull/168615.diff

6 Files Affected:

  • (modified) offload/liboffload/API/CMakeLists.txt (+1)
  • (added) offload/test/tools/offload-tblgen/get_info_wrappers.td (+57)
  • (modified) offload/tools/offload-tblgen/CMakeLists.txt (+1)
  • (modified) offload/tools/offload-tblgen/Generators.hpp (+2)
  • (added) offload/tools/offload-tblgen/TypedGetInfoWrappers.cpp (+84)
  • (modified) offload/tools/offload-tblgen/offload-tblgen.cpp (+7-1)
diff --git a/offload/liboffload/API/CMakeLists.txt b/offload/liboffload/API/CMakeLists.txt
index e4baa4772a1ef..daa8f382197df 100644
--- a/offload/liboffload/API/CMakeLists.txt
+++ b/offload/liboffload/API/CMakeLists.txt
@@ -19,6 +19,7 @@ offload_tablegen(OffloadEntryPoints.inc -gen-entry-points)
 offload_tablegen(OffloadFuncs.inc -gen-func-names)
 offload_tablegen(OffloadImplFuncDecls.inc -gen-impl-func-decls)
 offload_tablegen(OffloadPrint.hpp -gen-print-header)
+offload_tablegen(OffloadTypedGetInfo.inc -gen-get-info-wrappers)
 
 add_public_tablegen_target(OffloadGenerate)
 
diff --git a/offload/test/tools/offload-tblgen/get_info_wrappers.td b/offload/test/tools/offload-tblgen/get_info_wrappers.td
new file mode 100644
index 0000000000000..4483022c94fbc
--- /dev/null
+++ b/offload/test/tools/offload-tblgen/get_info_wrappers.td
@@ -0,0 +1,57 @@
+// RUN: %offload-tblgen -gen-get-info-wrappers -I %S/../../../liboffload/API %s | %fcheck-generic
+
+include "APIDefs.td"
+
+def ol_foo_handle_t : Handle {
+}
+
+def ol_foo_info_t : Enum {
+  let is_typed = 1;
+  let etors = [
+    TaggedEtor<"INT", "int", "">,
+    TaggedEtor<"STRING", "char[]", "">,
+    TaggedEtor<"ARRAY", "int[]", "">,
+  ];
+}
+
+def olGetFooInfo : Function {
+  let params = [
+    Param<"ol_foo_handle_t", "Foo", "", PARAM_IN>,
+    Param<"ol_foo_info_t", "PropName", "", PARAM_IN>,
+    Param<"size_t", "PropSize", "", PARAM_IN>,
+    TypeTaggedParam<"void*", "PropValue", "array of bytes holding the info.", PARAM_OUT,
+      TypeInfo<"PropName", "PropSize">>
+  ];
+  let returns = [
+    Return<"OL_FOO_INVALID">
+  ];
+}
+
+// CHECK-LABEL: template <ol_foo_info_t Desc> inline auto get_info(ol_foo_handle_t Foo);
+// CHECK-NEXT:  template<> inline auto get_info<OL_FOO_INFO_INT>(ol_foo_handle_t Foo) {
+// CHECK-NEXT:    int Result;
+// CHECK-NEXT:    if (auto Err = olGetFooInfo(Foo, OL_FOO_INFO_INT, 1, &Result))
+// CHECK-NEXT:      return std::variant<int, ol_result_t>{Err};
+// CHECK-NEXT:    else
+// CHECK-NEXT:      return std::variant<int, ol_result_t>{Result};
+// CHECK-NEXT:  }
+// CHECK-NEXT:  template<> inline auto get_info<OL_FOO_INFO_STRING>(ol_foo_handle_t Foo) {
+// CHECK-NEXT:    std::string Result;
+// CHECK-NEXT:    size_t ResultSize = 0;  if (auto Err = olGetFooInfoSize(Foo, OL_FOO_INFO_STRING, &ResultSize))
+// CHECK-NEXT:      return std::variant<std::string, ol_result_t>{Err};
+// CHECK-NEXT:    Result.resize(ResultSize);
+// CHECK-NEXT:    if (auto Err = olGetFooInfo(Foo, OL_FOO_INFO_STRING, ResultSize, Result.data()))
+// CHECK-NEXT:      return std::variant<std::string, ol_result_t>{Err};
+// CHECK-NEXT:    else
+// CHECK-NEXT:      return std::variant<std::string, ol_result_t>{Result};
+// CHECK-NEXT:  }
+// CHECK-NEXT:  template<> inline auto get_info<OL_FOO_INFO_ARRAY>(ol_foo_handle_t Foo) {
+// CHECK-NEXT:    std::vector<int> Result;
+// CHECK-NEXT:    size_t ResultSize = 0;  if (auto Err = olGetFooInfoSize(Foo, OL_FOO_INFO_ARRAY, &ResultSize))
+// CHECK-NEXT:      return std::variant<std::vector<int>, ol_result_t>{Err};
+// CHECK-NEXT:    Result.resize(ResultSize);
+// CHECK-NEXT:    if (auto Err = olGetFooInfo(Foo, OL_FOO_INFO_ARRAY, ResultSize, Result.data()))
+// CHECK-NEXT:      return std::variant<std::vector<int>, ol_result_t>{Err};
+// CHECK-NEXT:    else
+// CHECK-NEXT:      return std::variant<std::vector<int>, ol_result_t>{Result};
+// CHECK-NEXT:  }
diff --git a/offload/tools/offload-tblgen/CMakeLists.txt b/offload/tools/offload-tblgen/CMakeLists.txt
index a5ae1c3757fbf..bc3c4fa5b6ef7 100644
--- a/offload/tools/offload-tblgen/CMakeLists.txt
+++ b/offload/tools/offload-tblgen/CMakeLists.txt
@@ -20,6 +20,7 @@ add_tablegen(offload-tblgen OFFLOAD
   offload-tblgen.cpp
   PrintGen.cpp
   RecordTypes.hpp
+  TypedGetInfoWrappers.cpp
   )
 
 # Make sure that C++ headers are available, if libcxx is built at the same
diff --git a/offload/tools/offload-tblgen/Generators.hpp b/offload/tools/offload-tblgen/Generators.hpp
index fda63f8b198e5..84e14ea0c16e9 100644
--- a/offload/tools/offload-tblgen/Generators.hpp
+++ b/offload/tools/offload-tblgen/Generators.hpp
@@ -25,3 +25,5 @@ void EmitOffloadExports(const llvm::RecordKeeper &Records,
 void EmitOffloadErrcodes(const llvm::RecordKeeper &Records,
                          llvm::raw_ostream &OS);
 void EmitOffloadInfo(const llvm::RecordKeeper &Records, llvm::raw_ostream &OS);
+void EmitTypedGetInfoWrappers(const llvm::RecordKeeper &Records,
+                              llvm::raw_ostream &OS);
diff --git a/offload/tools/offload-tblgen/TypedGetInfoWrappers.cpp b/offload/tools/offload-tblgen/TypedGetInfoWrappers.cpp
new file mode 100644
index 0000000000000..5c1ef15ce3aff
--- /dev/null
+++ b/offload/tools/offload-tblgen/TypedGetInfoWrappers.cpp
@@ -0,0 +1,84 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 typed C++ inline wrappers for
+// various `olGet*Info interfaces.
+//
+//===----------------------------------------------------------------------===//
+
+#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;
+
+void EmitTypedGetInfoWrappers(const llvm::RecordKeeper &Records,
+                              llvm::raw_ostream &OS) {
+  OS << GenericHeader;
+  for (auto *R : Records.getAllDerivedDefinitions("Function")) {
+    auto Name = R->getName();
+    if (!Name.starts_with("olGet") || !Name.ends_with("Info"))
+      continue;
+    auto F = FunctionRec{R};
+    auto Params = F.getParams();
+    assert(Params.size() == 4);
+    auto Object = Params[0];
+    auto InfoDesc = Params[1];
+
+    OS << formatv("template <{} Desc> inline auto get_info({} {});\n",
+                  InfoDesc.getType(), Object.getType(), Object.getName());
+
+    EnumRec E{Records.getDef(InfoDesc.getType())};
+    for (auto &V : E.getValues()) {
+      auto Desc = E.getEnumValNamePrefix() + "_" + V.getName();
+      auto TaggedType = V.getTaggedType();
+      auto ResultType = [TaggedType]() -> std::string {
+        if (!TaggedType.ends_with("[]"))
+          return TaggedType.str();
+        if (TaggedType == "char[]")
+          return "std::string";
+
+        return ("std::vector<" + TaggedType.drop_back(2) + ">").str();
+      }();
+      auto ReturnType =
+          "std::variant<" + ResultType + ", " + PrefixLower + "_result_t>";
+      OS << formatv("template<> inline auto get_info<{}>({} {}) {{\n", Desc,
+                    Object.getType(), Object.getName());
+      if (TaggedType.ends_with("[]")) {
+        OS << TAB_1 << formatv("{0} Result;\n", ResultType);
+        OS << TAB_1 << "size_t ResultSize = 0;";
+        OS << TAB_1
+           << formatv("if (auto Err = {}Size({}, {}, &ResultSize))\n",
+                      F.getName(), Object.getName(), Desc);
+        OS << TAB_2 << formatv("return {}{{Err};\n", ReturnType);
+        OS << TAB_1 << "Result.resize(ResultSize);\n"; // TODO: Or "-1"?
+        OS << TAB_1
+           << formatv("if (auto Err = {}({}, {}, ResultSize, Result.data()))\n",
+                      F.getName(), Object.getName(), Desc);
+        OS << TAB_2 << formatv("return {0}{{Err};\n", ReturnType);
+        OS << TAB_1 << "else\n";
+        OS << TAB_2 << formatv("return {0}{{Result};\n", ReturnType);
+      } else {
+        OS << TAB_1 << formatv("{0} Result;\n", TaggedType);
+        OS << TAB_1
+           << formatv("if (auto Err = {}({}, {}, 1, &Result))\n", F.getName(),
+                      Object.getName(), Desc);
+        OS << TAB_2 << formatv("return {0}{{Err};\n", ReturnType);
+        OS << TAB_1 << "else\n";
+        OS << TAB_2 << formatv("return {0}{{Result};\n", ReturnType);
+      }
+      OS << "}\n";
+    }
+    OS << "\n";
+  }
+}
diff --git a/offload/tools/offload-tblgen/offload-tblgen.cpp b/offload/tools/offload-tblgen/offload-tblgen.cpp
index 18aaf9e00f08a..3a0f56acdc460 100644
--- a/offload/tools/offload-tblgen/offload-tblgen.cpp
+++ b/offload/tools/offload-tblgen/offload-tblgen.cpp
@@ -34,6 +34,7 @@ enum ActionType {
   GenExports,
   GenErrcodes,
   GenInfo,
+  GenTypedGetInfoWrappers,
 };
 
 namespace {
@@ -60,7 +61,10 @@ cl::opt<ActionType> Action(
                    "Generate export file for the Offload library"),
         clEnumValN(GenErrcodes, "gen-errcodes",
                    "Generate Offload Error Code enum"),
-        clEnumValN(GenInfo, "gen-info", "Generate Offload Info enum")));
+        clEnumValN(GenInfo, "gen-info", "Generate Offload Info enum"),
+        clEnumValN(GenTypedGetInfoWrappers, "gen-get-info-wrappers",
+                   "Generate typed C++ wrappers around various olGet*Info "
+                   "interfaces")));
 }
 
 static bool OffloadTableGenMain(raw_ostream &OS, const RecordKeeper &Records) {
@@ -98,6 +102,8 @@ static bool OffloadTableGenMain(raw_ostream &OS, const RecordKeeper &Records) {
   case GenInfo:
     EmitOffloadInfo(Records, OS);
     break;
+  case GenTypedGetInfoWrappers:
+    EmitTypedGetInfoWrappers(Records, OS);
   }
 
   return false;

@aelovikov-intel aelovikov-intel marked this pull request as ready for review November 18, 2025 23:07
@jhuber6
Copy link
Contributor

jhuber6 commented Nov 18, 2025

The intention of the offloading runtime is to be a C API. Are we providing these as additional wrappers?

@github-actions
Copy link

github-actions bot commented Nov 19, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@aelovikov-intel
Copy link
Contributor Author

aelovikov-intel commented Nov 19, 2025

The intention of the offloading runtime is to be a C API. Are we providing these as additional wrappers?

Yes, additional and only for in-tree C++ consumers, i.e., I'm not adding installs to

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_BINARY_DIR}/API/OffloadAPI.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include/offload)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/API/OffloadPrint.hpp DESTINATION ${CMAKE_INSTALL_PREFIX}/include/offload)

Expected usage will be something like #166927 (comment)

@aelovikov-intel
Copy link
Contributor Author

I've been thinking if there is a smaller/cheaper alternative.

  • This approach, fullest, provides the following usage
// get_info is provided/tblgen'ed by liboffload:
auto result = get_info<OL_DEVICE_INFO_SMTH>(device_handle);

but it mandates the mapping of how C types get translated into C++ type/error handling (e.g., choosing std::varaint/std::expected vs using C++ exceptions) and generates lots of similar code.

  • Smallest approach
// Technically, we can outline that helper in the "full" approach inside generated code as well.
// On the consumer side, pseudocode:
namespace detail {
template <auto InfoKind>
auto get_info(auto &InfoGetter, auto &InfoSizeGetter, auto Handle) {
  // ol_ret_type is provided/tblgen'ed by liboffload
  using ret_ty = typename ol_ret_type<decltype(InfoKind), InfoKind>::type;
  // generic implementation using InfoGetter, InfoSizeGetter, ret_ty
}
} // namespace detail
...
auto result = detail::get_info<OL_DEVICE_INFO_SMTH>(olGetDeviceInfo, olGetDeviceInfoSize, device_handle);

consumer can choose what types to use and how to handler errors. The actual logic is implemented in a single template instead of being duplicated in multiple template specializations. Minimal amount of tblgen'ed code inside liboffload. Maybe we can even put that into OffloadAPI.h under #if defined(__cplusplus), like this:

///////////////////////////////////////////////////////////////////////////////
/// @brief Variant of olWaitEvents that also sets source code location
/// information
/// @details See also ::olWaitEvents
OL_APIEXPORT ol_result_t OL_APICALL
olWaitEventsWithCodeLoc(ol_queue_handle_t Queue, ol_event_handle_t *Events,
                        size_t NumEvents, ol_code_location_t *CodeLocation);

#if defined(__cplusplus)
} // extern "C"
#endif

#if defined(__cplusplus)
template <typename, auto> struct ol_info_ret_type;
template <> struct ol_info_ret_type<ol_device_info_t, OL_DEVICE_INFO_TYPE> {
  using type = ol_device_type_t;
};
template <> struct ol_info_ret_type<ol_device_info_t, OL_DEVICE_INFO_PLATFORM> {
  using type = ol_platform_handle_t;
};
...
#endif
  • Middle ground, provide something like
template <typename, auto> struct info_traits;
template <> struct info_traits<ol_device_info_t, OL_DEVICE_INFO_SMTH> {
  using return_type = <smth>;
  // Maybe split these into a separate trait with just typename template param:
  static constexpr auto InfoGetter = &olGetDeviceInfo;
  static constexpr auto InfoSizeGetter = &olGetDeviceInfoSize;
};

@jhuber6 any preference between those three?

@jhuber6
Copy link
Contributor

jhuber6 commented Nov 19, 2025

Realistically all we need is a way to query the size from the runtime and then just make a template helper that passes a sufficient amount of data and casts it to the requested type. Do we have any runtime calls similar to that?

@aelovikov-intel
Copy link
Contributor Author

Realistically all we need is a way to query the size from the runtime and then just make a template helper that passes a sufficient amount of data and casts it to the requested type. Do we have any runtime calls similar to that?

Sorry, I don't understand that. The problem is that liboffload might be returning an int32_t via void *pOut yet the consumer might be mistakenly thinking that it's a float. The sizes would match and there is no way (currently) to detect such mismatch. After this patch the consumer would be able to verify (i.e., static_assert) what type do those bytes in pOut actually represent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants