diff --git a/cmake/onnxruntime.cmake b/cmake/onnxruntime.cmake index 64b31f665f56a..3b23f71160bf7 100644 --- a/cmake/onnxruntime.cmake +++ b/cmake/onnxruntime.cmake @@ -32,6 +32,7 @@ function(get_c_cxx_api_headers HEADERS_VAR) "${REPO_ROOT}/include/onnxruntime/core/session/onnxruntime_error_code.h" "${REPO_ROOT}/include/onnxruntime/core/session/onnxruntime_experimental_c_api.h" "${REPO_ROOT}/include/onnxruntime/core/session/onnxruntime_experimental_c_api.inc" + "${REPO_ROOT}/include/onnxruntime/core/session/onnxruntime_experimental_cxx_api.h" "${REPO_ROOT}/include/onnxruntime/core/session/onnxruntime_float16.h" "${REPO_ROOT}/include/onnxruntime/core/session/onnxruntime_lite_custom_op.h" "${REPO_ROOT}/include/onnxruntime/core/session/onnxruntime_run_options_config_keys.h" diff --git a/docs/design/Experimental_C_API.md b/docs/design/Experimental_C_API.md index ba7178ac33460..6105e3106c037 100644 --- a/docs/design/Experimental_C_API.md +++ b/docs/design/Experimental_C_API.md @@ -103,17 +103,27 @@ ORT_EXPERIMENTAL_API(22, OrtStatusPtr, OrtApi_AnotherThing, _In_ const OrtEnv* env, _In_ const char* name, _Out_ OrtValue** out) ``` -### Experimental Consumer Header (generated from `.inc`) +### Experimental Consumer Headers (generated from `.inc`) -A single header serves both C and C++ experimental API consumers. The C section provides typedefs and name -constants; the C++ section (guarded by `#ifdef __cplusplus`) adds typed inline accessors in the `Ort::Experimental` -namespace. +Two headers serve experimental API consumers, mirroring the `onnxruntime_c_api.h` / `onnxruntime_cxx_api.h` split: + +- `onnxruntime_experimental_c_api.h` — pure C. Provides the function pointer typedefs and name constants, plus any + auxiliary opaque type declarations the experimental APIs require. +- `onnxruntime_experimental_cxx_api.h` — C++ companion. Includes the C header (and `onnxruntime_cxx_api.h`) and adds + typed inline accessors in the `Ort::Experimental` namespace. Any C++ wrapper types associated with the experimental + APIs should also be defined in this header. + +The `.inc` file remains the single source of truth. Each public header performs the appropriate X-macro pass inline, +keeping the declaration list centralized. ```c -// onnxruntime_experimental_c_api.h +// onnxruntime_experimental_c_api.h (public) #pragma once +#include "onnxruntime_c_api.h" + // Declare any new, auxiliary opaque types required by the experimental APIs in this header too. +// ORT_RUNTIME_CLASS(...); // --- C: function pointer typedefs and name constants --- #define ORT_EXPERIMENTAL_API(VER, RET, NAME, ...) \ @@ -127,35 +137,67 @@ namespace. // ...) NO_EXCEPTION; // static const char* const kOrtExperimental_OrtApi_SomeNewThing_SinceV22_FnName = // "OrtApi_SomeNewThing_SinceV22"; +``` + +The C++ header generates two accessor flavors per function: a nullable accessor (returns `nullptr` if the function is +unavailable) and a throwing accessor (`...FnOrThrow`, throws `Ort::Exception` with `ORT_NOT_IMPLEMENTED` if the function +is unavailable). The nullable accessor is for runtime availability checks; the throwing accessor is for when the +function is required. + +```cpp +// onnxruntime_experimental_cxx_api.h (public) +#pragma once + +#include "onnxruntime_experimental_c_api.h" +#include "onnxruntime_cxx_api.h" // for Ort::Exception / ORT_CXX_API_THROW -#ifdef __cplusplus namespace Ort { namespace Experimental { -// --- C++: typed inline accessors (reuses the C typedefs above) --- -#define ORT_EXPERIMENTAL_API(VER, RET, NAME, ...) \ - inline OrtExperimental_##NAME##_SinceV##VER##_Fn Get_##NAME##_SinceV##VER##_Fn( \ - const OrtApi* api) { \ - return reinterpret_cast( \ - api->GetExperimentalFunction(kOrtExperimental_##NAME##_SinceV##VER##_FnName)); \ +// --- C++: nullable typed inline accessors (reuses the C typedefs) --- +#define ORT_EXPERIMENTAL_API(VER, RET, NAME, ...) \ + inline OrtExperimental_##NAME##_SinceV##VER##_Fn Get_##NAME##_SinceV##VER##_Fn( \ + const OrtApi* api) { \ + return reinterpret_cast( \ + api->GetExperimentalFunction(kOrtExperimental_##NAME##_SinceV##VER##_FnName)); \ } #include "onnxruntime_experimental_c_api.inc" #undef ORT_EXPERIMENTAL_API -} // namespace Experimental -} // namespace Ort +// --- C++: throwing typed inline accessors (reuse the nullable accessors) --- +#define ORT_EXPERIMENTAL_API(VER, RET, NAME, ...) \ + inline OrtExperimental_##NAME##_SinceV##VER##_Fn Get_##NAME##_SinceV##VER##_FnOrThrow( \ + const OrtApi* api) { \ + auto* fn = Get_##NAME##_SinceV##VER##_Fn(api); \ + if (fn == nullptr) { \ + ORT_CXX_API_THROW( \ + "Experimental function " #NAME "_SinceV" #VER " is not available in this build", \ + ORT_NOT_IMPLEMENTED); \ + } \ + return fn; \ + } +#include "onnxruntime_experimental_c_api.inc" +#undef ORT_EXPERIMENTAL_API // Produces (for SinceVersion=22, Name=OrtApi_SomeNewThing): -// namespace Ort { -// namespace Experimental { // inline OrtExperimental_OrtApi_SomeNewThing_SinceV22_Fn // Get_OrtApi_SomeNewThing_SinceV22_Fn(const OrtApi* api) { // return reinterpret_cast( // api->GetExperimentalFunction(kOrtExperimental_OrtApi_SomeNewThing_SinceV22_FnName)); // } -// } -// } -#endif // __cplusplus +// inline OrtExperimental_OrtApi_SomeNewThing_SinceV22_Fn +// Get_OrtApi_SomeNewThing_SinceV22_FnOrThrow(const OrtApi* api) { +// auto* fn = Get_OrtApi_SomeNewThing_SinceV22_Fn(api); +// if (fn == nullptr) { +// ORT_CXX_API_THROW( +// "Experimental function OrtApi_SomeNewThing_SinceV22 is not available in this build", +// ORT_NOT_IMPLEMENTED); +// } +// return fn; +// } + +} // namespace Experimental +} // namespace Ort ``` C usage: @@ -169,7 +211,7 @@ if (fn) { } ``` -C++ usage: +C++ usage (nullable): ```cpp if (auto* fn = Ort::Experimental::Get_OrtApi_SomeNewThing_SinceV22_Fn(api)) { @@ -177,6 +219,13 @@ if (auto* fn = Ort::Experimental::Get_OrtApi_SomeNewThing_SinceV22_Fn(api)) { } ``` +C++ usage (throwing): + +```cpp +auto* fn = Ort::Experimental::Get_OrtApi_SomeNewThing_SinceV22_FnOrThrow(api); +Ort::Status status(fn(session, &result)); +``` + ### Implementation Side (generated from `.inc`) ```cpp diff --git a/include/onnxruntime/core/session/onnxruntime_experimental_c_api.h b/include/onnxruntime/core/session/onnxruntime_experimental_c_api.h index 0dd87c10776d3..e5b9bd4713a1c 100644 --- a/include/onnxruntime/core/session/onnxruntime_experimental_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_experimental_c_api.h @@ -3,15 +3,18 @@ // Experimental C API consumer header. // -// This header provides typedefs, name constants, and (for C++) typed inline accessors for experimental ORT functions. -// It should be used together with the experimental header lookup function `OrtApi::GetExperimentalFunction()`. +// This header provides C function pointer typedefs and name constants for experimental ORT API functions, as well as +// any auxiliary declarations required by the experimental API functions. // -// This header contains code generated from onnxruntime_experimental_c_api.inc, which defines the list of experimental -// API functions. +// The function pointer typedefs and name constants should be used together with the experimental API lookup function +// `OrtApi::GetExperimentalFunction()`. A function's availability should always be checked at runtime (the lookup +// returns nullptr if the function is not present). +// +// C++ API consumers should use the companion header, onnxruntime_experimental_cxx_api.h, which provides typed +// accessors for the experimental API functions. // // IMPORTANT: Experimental functions are NOT part of the stable ABI. They may be added, changed, or removed between -// releases without notice. A function's availability should always be checked at runtime (the lookup returns nullptr -// if the function is not present). +// releases without notice. Anything in this file should be treated as experimental and unstable. // // C usage: // OrtExperimental_OrtApi_ExperimentalApiTest_SinceV28_Fn fn = @@ -21,7 +24,7 @@ // OrtStatusPtr status = fn(&result); // } // -// C++ usage: +// C++ usage (see onnxruntime_experimental_cxx_api.h): // if (auto* fn = Ort::Experimental::Get_OrtApi_ExperimentalApiTest_SinceV28_Fn(api)) { // Ort::Status status(fn(&result)); // } @@ -44,7 +47,7 @@ ORT_RUNTIME_CLASS(ModelPackageContext); ORT_RUNTIME_CLASS(ModelPackageComponentContext); // -// C: function pointer typedefs and name constants +// C function pointer typedefs and name constants // // For each ORT_EXPERIMENTAL_API(VER, RET, NAME, ...) entry in the .inc file, this generates: @@ -67,38 +70,3 @@ ORT_RUNTIME_CLASS(ModelPackageComponentContext); #include "onnxruntime_experimental_c_api.inc" #undef ORT_EXPERIMENTAL_API - -// -// C++: typed inline accessors -// - -#ifdef __cplusplus - -namespace Ort { -namespace Experimental { - -// For each .inc entry, this generates a typed accessor in Ort::Experimental: -// -// inline OrtExperimental__SinceV_Fn Get__SinceV_Fn(const OrtApi* api); -// -// Example: ORT_EXPERIMENTAL_API(28, OrtStatusPtr, OrtApi_ExperimentalApiTest, ...) produces: -// inline OrtExperimental_OrtApi_ExperimentalApiTest_SinceV28_Fn -// Get_OrtApi_ExperimentalApiTest_SinceV28_Fn(const OrtApi* api) { -// return reinterpret_cast( -// api->GetExperimentalFunction(kOrtExperimental_OrtApi_ExperimentalApiTest_SinceV28_FnName)); -// } -#define ORT_EXPERIMENTAL_API(VER, RET, NAME, ...) \ - inline OrtExperimental_##NAME##_SinceV##VER##_Fn Get_##NAME##_SinceV##VER##_Fn( \ - const OrtApi* api) { \ - return reinterpret_cast( \ - api->GetExperimentalFunction(kOrtExperimental_##NAME##_SinceV##VER##_FnName)); \ - } - -#include "onnxruntime_experimental_c_api.inc" - -#undef ORT_EXPERIMENTAL_API - -} // namespace Experimental -} // namespace Ort - -#endif // __cplusplus diff --git a/include/onnxruntime/core/session/onnxruntime_experimental_cxx_api.h b/include/onnxruntime/core/session/onnxruntime_experimental_cxx_api.h new file mode 100644 index 0000000000000..fbd7ba3659435 --- /dev/null +++ b/include/onnxruntime/core/session/onnxruntime_experimental_cxx_api.h @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// Experimental C++ API consumer header. +// +// This header provides typed inline accessors in the Ort::Experimental namespace for experimental ORT API functions, +// and any C++ wrapper types associated with the experimental API functions. +// +// It is the C++ companion to onnxruntime_experimental_c_api.h. +// +// IMPORTANT: Experimental functions are NOT part of the stable ABI. They may be added, changed, or removed between +// releases without notice. Anything in this file should be treated as experimental and unstable. +// +// Two accessor flavors are generated for each experimental function: +// +// 1. Get__SinceV_Fn(api) +// Returns the typed function pointer, or nullptr if the function is not available in this build. +// Use this to check availability at runtime. +// +// 2. Get__SinceV_FnOrThrow(api) +// Returns the typed function pointer, or throws Ort::Exception (ORT_NOT_IMPLEMENTED) if the function is not +// available in this build. +// Use this when the function is required. +// +// C++ usage (nullable): +// if (auto* fn = Ort::Experimental::Get_OrtApi_ExperimentalApiTest_SinceV28_Fn(api)) { +// Ort::Status status(fn(&result)); +// } +// +// C++ usage (throwing): +// auto* fn = Ort::Experimental::Get_OrtApi_ExperimentalApiTest_SinceV28_FnOrThrow(api); +// Ort::Status status(fn(&result)); + +#pragma once + +#include "onnxruntime_experimental_c_api.h" +#include "onnxruntime_cxx_api.h" + +namespace Ort { +namespace Experimental { + +// +// Nullable typed accessors +// + +// For each .inc entry, this generates a typed accessor in Ort::Experimental: +// +// inline OrtExperimental__SinceV_Fn Get__SinceV_Fn(const OrtApi* api); +// +// Example: ORT_EXPERIMENTAL_API(28, OrtStatusPtr, OrtApi_ExperimentalApiTest, ...) produces: +// inline OrtExperimental_OrtApi_ExperimentalApiTest_SinceV28_Fn +// Get_OrtApi_ExperimentalApiTest_SinceV28_Fn(const OrtApi* api) { +// return reinterpret_cast( +// api->GetExperimentalFunction(kOrtExperimental_OrtApi_ExperimentalApiTest_SinceV28_FnName)); +// } +#define ORT_EXPERIMENTAL_API(VER, RET, NAME, ...) \ + inline OrtExperimental_##NAME##_SinceV##VER##_Fn Get_##NAME##_SinceV##VER##_Fn( \ + const OrtApi* api) { \ + return reinterpret_cast( \ + api->GetExperimentalFunction(kOrtExperimental_##NAME##_SinceV##VER##_FnName)); \ + } + +#include "onnxruntime_experimental_c_api.inc" + +#undef ORT_EXPERIMENTAL_API + +// +// Throwing typed accessors +// + +// For each .inc entry, this generates a throwing accessor in Ort::Experimental: +// +// inline OrtExperimental__SinceV_Fn Get__SinceV_FnOrThrow(const OrtApi* api); +// +// It returns the non-null typed function pointer, or throws Ort::Exception (ORT_NOT_IMPLEMENTED) if the function is +// not available in this build. +// +// Example: ORT_EXPERIMENTAL_API(28, OrtStatusPtr, OrtApi_ExperimentalApiTest, ...) produces: +// inline OrtExperimental_OrtApi_ExperimentalApiTest_SinceV28_Fn +// Get_OrtApi_ExperimentalApiTest_SinceV28_FnOrThrow(const OrtApi* api) { +// auto* fn = Get_OrtApi_ExperimentalApiTest_SinceV28_Fn(api); +// if (fn == nullptr) { +// ORT_CXX_API_THROW( +// "Experimental function OrtApi_ExperimentalApiTest_SinceV28 is not available in this build", +// ORT_NOT_IMPLEMENTED); +// } +// return fn; +// } +#define ORT_EXPERIMENTAL_API(VER, RET, NAME, ...) \ + inline OrtExperimental_##NAME##_SinceV##VER##_Fn Get_##NAME##_SinceV##VER##_FnOrThrow( \ + const OrtApi* api) { \ + auto* fn = Get_##NAME##_SinceV##VER##_Fn(api); \ + if (fn == nullptr) { \ + ORT_CXX_API_THROW( \ + "Experimental function " #NAME "_SinceV" #VER " is not available in this build", \ + ORT_NOT_IMPLEMENTED); \ + } \ + return fn; \ + } + +#include "onnxruntime_experimental_c_api.inc" + +#undef ORT_EXPERIMENTAL_API + +// +// Auxiliary types and helpers +// +// C++ wrapper types or helpers go here in the `Ort::Experimental` namespace. +// + +} // namespace Experimental +} // namespace Ort diff --git a/onnxruntime/test/autoep/test_model_package.cc b/onnxruntime/test/autoep/test_model_package.cc index 34f73eb69b149..ee5c8bb567e1e 100644 --- a/onnxruntime/test/autoep/test_model_package.cc +++ b/onnxruntime/test/autoep/test_model_package.cc @@ -12,7 +12,7 @@ #include "gtest/gtest.h" #include "core/session/model_package/model_package_context.h" -#include "core/session/onnxruntime_experimental_c_api.h" +#include "core/session/onnxruntime_experimental_cxx_api.h" #include "core/session/abi_devices.h" #include "test/autoep/test_autoep_utils.h" #include "test/util/include/asserts.h" @@ -62,47 +62,38 @@ struct ModelPackageFns { inline const ModelPackageFns& GetModelPackageFns() { static const ModelPackageFns fns = []() { const OrtApi* api = &Ort::GetApi(); + namespace Exp = Ort::Experimental; ModelPackageFns f; -#define RESOLVE(member, getter) \ - do { \ - f.member = Ort::Experimental::getter(api); \ - if (f.member == nullptr) { \ - throw std::runtime_error(std::string("Failed to resolve experimental " \ - "OrtModelPackageApi_") + \ - #member "_SinceV28"); \ - } \ - } while (0) - RESOLVE(CreateModelPackageOptionsFromSessionOptions, - Get_OrtModelPackageApi_CreateModelPackageOptionsFromSessionOptions_SinceV28_Fn); - RESOLVE(ReleaseModelPackageOptions, - Get_OrtModelPackageApi_ReleaseModelPackageOptions_SinceV28_Fn); - RESOLVE(CreateModelPackageContext, - Get_OrtModelPackageApi_CreateModelPackageContext_SinceV28_Fn); - RESOLVE(ReleaseModelPackageContext, - Get_OrtModelPackageApi_ReleaseModelPackageContext_SinceV28_Fn); - RESOLVE(ModelPackage_GetSchemaVersion, - Get_OrtModelPackageApi_ModelPackage_GetSchemaVersion_SinceV28_Fn); - RESOLVE(ModelPackage_GetComponentCount, - Get_OrtModelPackageApi_ModelPackage_GetComponentCount_SinceV28_Fn); - RESOLVE(ModelPackage_GetComponentNames, - Get_OrtModelPackageApi_ModelPackage_GetComponentNames_SinceV28_Fn); - RESOLVE(ModelPackage_GetVariantCount, - Get_OrtModelPackageApi_ModelPackage_GetVariantCount_SinceV28_Fn); - RESOLVE(ModelPackage_GetVariantNames, - Get_OrtModelPackageApi_ModelPackage_GetVariantNames_SinceV28_Fn); - RESOLVE(ModelPackage_GetVariantEpName, - Get_OrtModelPackageApi_ModelPackage_GetVariantEpName_SinceV28_Fn); - RESOLVE(SelectComponent, - Get_OrtModelPackageApi_SelectComponent_SinceV28_Fn); - RESOLVE(ReleaseModelPackageComponentContext, - Get_OrtModelPackageApi_ReleaseModelPackageComponentContext_SinceV28_Fn); - RESOLVE(ModelPackageComponent_GetSelectedVariantName, - Get_OrtModelPackageApi_ModelPackageComponent_GetSelectedVariantName_SinceV28_Fn); - RESOLVE(ModelPackageComponent_GetSelectedVariantFolderPath, - Get_OrtModelPackageApi_ModelPackageComponent_GetSelectedVariantFolderPath_SinceV28_Fn); - RESOLVE(CreateSession, - Get_OrtModelPackageApi_CreateSession_SinceV28_Fn); -#undef RESOLVE + f.CreateModelPackageOptionsFromSessionOptions = + Exp::Get_OrtModelPackageApi_CreateModelPackageOptionsFromSessionOptions_SinceV28_FnOrThrow(api); + f.ReleaseModelPackageOptions = + Exp::Get_OrtModelPackageApi_ReleaseModelPackageOptions_SinceV28_FnOrThrow(api); + f.CreateModelPackageContext = + Exp::Get_OrtModelPackageApi_CreateModelPackageContext_SinceV28_FnOrThrow(api); + f.ReleaseModelPackageContext = + Exp::Get_OrtModelPackageApi_ReleaseModelPackageContext_SinceV28_FnOrThrow(api); + f.ModelPackage_GetSchemaVersion = + Exp::Get_OrtModelPackageApi_ModelPackage_GetSchemaVersion_SinceV28_FnOrThrow(api); + f.ModelPackage_GetComponentCount = + Exp::Get_OrtModelPackageApi_ModelPackage_GetComponentCount_SinceV28_FnOrThrow(api); + f.ModelPackage_GetComponentNames = + Exp::Get_OrtModelPackageApi_ModelPackage_GetComponentNames_SinceV28_FnOrThrow(api); + f.ModelPackage_GetVariantCount = + Exp::Get_OrtModelPackageApi_ModelPackage_GetVariantCount_SinceV28_FnOrThrow(api); + f.ModelPackage_GetVariantNames = + Exp::Get_OrtModelPackageApi_ModelPackage_GetVariantNames_SinceV28_FnOrThrow(api); + f.ModelPackage_GetVariantEpName = + Exp::Get_OrtModelPackageApi_ModelPackage_GetVariantEpName_SinceV28_FnOrThrow(api); + f.SelectComponent = + Exp::Get_OrtModelPackageApi_SelectComponent_SinceV28_FnOrThrow(api); + f.ReleaseModelPackageComponentContext = + Exp::Get_OrtModelPackageApi_ReleaseModelPackageComponentContext_SinceV28_FnOrThrow(api); + f.ModelPackageComponent_GetSelectedVariantName = + Exp::Get_OrtModelPackageApi_ModelPackageComponent_GetSelectedVariantName_SinceV28_FnOrThrow(api); + f.ModelPackageComponent_GetSelectedVariantFolderPath = + Exp::Get_OrtModelPackageApi_ModelPackageComponent_GetSelectedVariantFolderPath_SinceV28_FnOrThrow(api); + f.CreateSession = + Exp::Get_OrtModelPackageApi_CreateSession_SinceV28_FnOrThrow(api); return f; }(); return fns; diff --git a/onnxruntime/test/shared_lib/test_experimental_api.cc b/onnxruntime/test/shared_lib/test_experimental_api.cc index d5bdc16ac3f54..893424e5d886c 100644 --- a/onnxruntime/test/shared_lib/test_experimental_api.cc +++ b/onnxruntime/test/shared_lib/test_experimental_api.cc @@ -3,7 +3,7 @@ #include "core/session/onnxruntime_c_api.h" #include "core/session/onnxruntime_cxx_api.h" -#include "core/session/onnxruntime_experimental_c_api.h" +#include "core/session/onnxruntime_experimental_cxx_api.h" #include "gtest/gtest.h" @@ -50,10 +50,14 @@ TEST_F(ExperimentalCApiTest, KnownNameResolvesCpp) { EXPECT_NE(fn, nullptr); } -// Call through typed pointer succeeds and returns the expected sentinel value +// Call through typed pointer succeeds and returns the expected sentinel value. +// Uses the throwing accessor (FnOrThrow), which returns a guaranteed-non-null pointer or throws. +// The throw-on-unavailable path is not directly unit-testable: every .inc entry is registered, so a valid +// typed accessor never observes a nullptr. Availability checking is covered by the nullable-accessor tests above. TEST_F(ExperimentalCApiTest, CallThroughTypedPointer) { - auto* fn = Ort::Experimental::Get_OrtApi_ExperimentalApiTest_SinceV28_Fn(api_); + auto* fn = Ort::Experimental::Get_OrtApi_ExperimentalApiTest_SinceV28_FnOrThrow(api_); ASSERT_NE(fn, nullptr); + EXPECT_EQ(fn, Ort::Experimental::Get_OrtApi_ExperimentalApiTest_SinceV28_Fn(api_)); int64_t result = 0; auto status = Ort::Status{fn(&result)};