Reland #118503: [Offload] Introduce offload-tblgen and initial new API implementation (#118614)

Reland #118503. Added a fix for builds with `-DBUILD_SHARED_LIBS=ON`
(see last commit). Otherwise the changes are identical.

---


### New API

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

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

### Demoing the new API

```sh
# From the runtime build directory
$ ninja LibomptUnitTests
$ OFFLOAD_TRACE=1 ./offload/unittests/OffloadAPI/offload.unittests 
```


### Open questions and future work
* Only some of the available device info is exposed, and not all the
possible device queries needed for SYCL are implemented by the plugins.
A sensible next step would be to refactor and extend the existing device
info queries in the plugins. The existing info queries are all strings,
but the new API introduces the ability to return any arbitrary type.
* It may be sensible at some point for the plugins to implement the new
API directly, and the higher level code on top of it could be made
generic, but this is more of a long-term possibility.
This commit is contained in:
Callum Fare
2024-12-05 08:34:04 +00:00
committed by GitHub
parent 636beb6a28
commit fd3907ccb5
56 changed files with 4928 additions and 2 deletions

View File

@@ -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))

View File

@@ -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")

View File

@@ -0,0 +1,40 @@
// RUN: %offload-tblgen -gen-api -I %S/../../../liboffload/API %s | %fcheck-generic --check-prefix=CHECK-API
// RUN: %offload-tblgen -gen-entry-points -I %S/../../../liboffload/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 = "ol_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<"ol_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: OL_RESULT_SUCCESS
// CHECK-API: OL_ERRC_INVALID_NULL_HANDLE
// CHECK-API-NEXT: `NULL == ParamHandle`
// CHECK-API: OL_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 OL_ERRC_INVALID_NULL_HANDLE;
// CHECK-VALIDATION: if (NULL == ParamPointer)
// CHECK-VALIDATION-NEXT: return OL_ERRC_INVALID_NULL_POINTER;
// CHECK-VALIDATION-NOT: if (NULL == ParamPointerOpt)

View File

@@ -0,0 +1,37 @@
// RUN: %offload-tblgen -gen-entry-points -I %S/../../../liboffload/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<"OL_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: ol_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;

View File

@@ -0,0 +1,39 @@
// RUN: %offload-tblgen -gen-api -I %S/../../../liboffload/API %s | %fcheck-generic --check-prefix=CHECK-API
// RUN: %offload-tblgen -gen-exports -I %S/../../../liboffload/API %s | %fcheck-generic --check-prefix=CHECK-EXPORTS
// RUN: %offload-tblgen -gen-func-names -I %S/../../../liboffload/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<"OL_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: OL_ERRC_INVALID_VALUE
// CHECK-API-NEXT: When a value is invalid
// CHECK-API: ol_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)

View File

@@ -0,0 +1,26 @@
// RUN: %offload-tblgen -gen-api -I %S/../../../liboffload/API %s | %fcheck-generic --check-prefix=CHECK-API
// RUN: %offload-tblgen -gen-exports -I %S/../../../liboffload/API %s | %fcheck-generic --check-prefix=CHECK-EXPORTS
// RUN: %offload-tblgen -gen-func-names -I %S/../../../liboffload/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<"OL_ERRC_INVALID_VALUE", ["When a value is invalid"]>
];
}
// CHECK-API-DAG: ol_result_t{{.*}} FunctionA
// CHECK-API-DAG: ol_result_t{{.*}} FunctionAWithCodeLoc
// CHECK-EXPORTS: FunctionAWithCodeLoc
// CHECK-FUNC-MACRO: OFFLOAD_FUNC(FunctionAWithCodeLoc)

View File

@@ -0,0 +1,36 @@
// RUN: %offload-tblgen -gen-print-header -I %S/../../../liboffload/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 << "}";

View File

@@ -0,0 +1,34 @@
// RUN: %offload-tblgen -gen-print-header -I %S/../../../liboffload/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;

View File

@@ -0,0 +1,38 @@
// RUN: %offload-tblgen -gen-print-header -I %S/../../../liboffload/API %s | %fcheck-generic --check-prefix=CHECK-PRINT
// RUN: %offload-tblgen -gen-api -I %S/../../../liboffload/API %s | %fcheck-generic --check-prefix=CHECK-API
// Check that print helpers are created for functions
include "APIDefs.td"
def : Handle {
let name = "ol_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<"ol_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: ol_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);

View File

@@ -0,0 +1,76 @@
// RUN: %offload-tblgen -gen-api -I %S/../../../liboffload/API %s | %fcheck-generic --check-prefix=CHECK-API
// RUN: %offload-tblgen -gen-print-header -I %S/../../../liboffload/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 OL_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);