From 5fb3d0e08076e550ea7af2e0e40fba7a3dba7618 Mon Sep 17 00:00:00 2001 From: Christopher Warrington Date: Mon, 9 Jun 2025 18:34:03 -0700 Subject: [PATCH 1/7] Eliminate dynamic initialization of static Ort::Global::api_ When ORT_API_MANUAL_INIT is not defined (which is the default), the static `Ort::Global::api_` has a dynamic initializer that calls `OrtGetApiBase()->GetApi(ORT_API_VERSION)` This dynamic initialization can cause problems when it interacts with other global/static initialization. On Windows in particular, it can also cause deadlocks when used in a dynamic library if OrtGetApiBase()->GetApi() attempts to load any other libraries. Delay the call to `OrtGetApiBase()` until the first call to `Ort::GetApi()` so that `OrtGetApiBase()` is typically called after dynamic library loading. * Replace the templated `Global::api_` with an inline static initialized to nullptr. * `Ort::GetApi()` now calls `detail::Global::GetApi()` which calls `detail::Global::DefaultInit()` if initialization is needed. * When `ORT_API_MANUAL_INIT` is defined, `DefaultInit()` returns nullptr, which will eventually cause the program to crash. The callers have violated the initialization contract by not calling one of the `Ort::InitApi` overloads. * When `ORT_API_MANUAL_INIT` is not defined, `DefaultInit()` uses a function-level static to compute the result of `OrtGetApiBase()->GetApi(ORT_API_VERSION)` once and return it. * `Ort::Global` has been replaced with a non-templated type and moved inside a `detail` namespace. Since the `Global` object was documented as being used internally, it is believed that these changes here are non-breaking, as they do not impact a public API. The public APIs, `Ort::InitApi()` and `Ort::InitApi(const OrtApi*)` remain unchanged. * Add `#pragma detect_mismatch` to surface issues with compilation units that disagree on how ORT_API_MANUAL_INIT is defined. (Clang and MSVC only.) --- .../core/session/onnxruntime_cxx_api.h | 107 +++++++++++++----- .../shared_library/provider_ort_api_init.cc | 4 +- onnxruntime/test/autoep/library/ep_arena.h | 3 + .../custom_op_library/custom_op_library.cc | 2 +- 4 files changed, 85 insertions(+), 31 deletions(-) diff --git a/include/onnxruntime/core/session/onnxruntime_cxx_api.h b/include/onnxruntime/core/session/onnxruntime_cxx_api.h index d1b08f127fa2a..272459ef97113 100644 --- a/include/onnxruntime/core/session/onnxruntime_cxx_api.h +++ b/include/onnxruntime/core/session/onnxruntime_cxx_api.h @@ -79,22 +79,17 @@ struct Exception : std::exception { throw Ort::Exception(string, code) #endif -// This is used internally by the C++ API. This class holds the global variable that points to the OrtApi, -// it's in a template so that we can define a global variable in a header and make -// it transparent to the users of the API. -template -struct Global { - static const OrtApi* api_; -}; - -// If macro ORT_API_MANUAL_INIT is defined, no static initialization will be performed. Instead, user must call InitApi() before using it. -template -#ifdef ORT_API_MANUAL_INIT -const OrtApi* Global::api_{}; -inline void InitApi() noexcept { Global::api_ = OrtGetApiBase()->GetApi(ORT_API_VERSION); } - -// Used by custom operator libraries that are not linked to onnxruntime. Sets the global API object, which is -// required by C++ APIs. +// If macro ORT_API_MANUAL_INIT is defined, no static initialization will be +// performed. Instead, users must call InitApi() before using ORT. +// +// InitApi() sets the global API object using the default initialization +// logic. Users call this to initialize the ORT C++ APIs at a time that +// makes sense in their program. +inline void InitApi() noexcept; + +// InitApi(const OrtApi*) is used by custom operator libraries that are not +// linked to onnxruntime. It sets the global API object, which is required +// by the ORT C++ APIs. // // Example mycustomop.cc: // @@ -107,22 +102,78 @@ inline void InitApi() noexcept { Global::api_ = OrtGetApiBase()->GetApi(OR // // ... // } // -inline void InitApi(const OrtApi* api) noexcept { Global::api_ = api; } -#else -#if defined(_MSC_VER) && !defined(__clang__) -#pragma warning(push) -// "Global initializer calls a non-constexpr function." Therefore you can't use ORT APIs in the other global initializers. -// Please define ORT_API_MANUAL_INIT if it conerns you. -#pragma warning(disable : 26426) -#endif -const OrtApi* Global::api_ = OrtGetApiBase()->GetApi(ORT_API_VERSION); -#if defined(_MSC_VER) && !defined(__clang__) -#pragma warning(pop) +inline void InitApi(const OrtApi* api) noexcept; + +namespace detail { +// This is used internally by the C++ API. This class holds the global +// variable that points to the OrtApi. +struct Global { + static const OrtApi* GetApi() noexcept { + if (!api_) { + api_ = DefaultInit(); + } + + return api_; + } + + private: + inline static const OrtApi* api_ = nullptr; + + // Has different definitions based on ORT_API_MANUAL_INIT + static const OrtApi* DefaultInit() noexcept; + + // Public APIs to set the OrtApi* to use. + friend void ::Ort::InitApi() noexcept; + friend void ::Ort::InitApi(const OrtApi*) noexcept; +}; +} // namespace detail + +#ifdef ORT_API_MANUAL_INIT + +inline void InitApi(const OrtApi* api) noexcept { detail::Global::api_ = api; } +inline void InitApi() noexcept { InitApi(OrtGetApiBase()->GetApi(ORT_API_VERSION)); } + +#if defined(__clang__) || defined(_MSC_VER) +// If you get a linker error about a mismatch here, you are trying to +// link two compilation units that have different definitions for +// ORT_API_MANUAL_INIT together. All compilation units must agree on the +// definition of ORT_API_MANUAL_INIT. +#pragma detect_mismatch("ORT_API_MANUAL_INIT", "enabled") #endif + +inline const OrtApi* detail::Global::DefaultInit() noexcept { + // When ORT_API_MANUAL_INIT is defined, there's no default init that can + // be done. + return nullptr; +} + +#else // ORT_API_MANUAL_INIT + +#if defined(__clang__) || defined(_MSC_VER) +// If you get a linker error about a mismatch here, you are trying to link +// two compilation units that have different definitions for +// ORT_API_MANUAL_INIT together. All compilation units must agree on the +// definition of ORT_API_MANUAL_INIT. +#pragma detect_mismatch("ORT_API_MANUAL_INIT", "disabled") #endif +inline const OrtApi* detail::Global::DefaultInit() noexcept { + // This block-level static will be initialized once when this function is + // first executed, delaying the call to OrtGetApiBase()->GetApi() until it + // is first needed. This helps avoid issues with static initialization + // order and dynamic libraries loading other dynamic libraries. + // + // This makes it safe to have a Ort::Env constructed as a static member. + // + // This DOES NOT make it safe to _use_ arbitrary ORT C++ APIs when + // initializaing static members, however. + static const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); + return api; +} +#endif // ORT_API_MANUAL_INIT + /// This returns a reference to the ORT C API. -inline const OrtApi& GetApi() noexcept { return *Global::api_; } +inline const OrtApi& GetApi() noexcept { return *detail::Global::GetApi(); } /// /// This function returns the onnxruntime version string diff --git a/onnxruntime/core/providers/shared_library/provider_ort_api_init.cc b/onnxruntime/core/providers/shared_library/provider_ort_api_init.cc index 9fa2551e53c23..f8d88b07f6dd5 100644 --- a/onnxruntime/core/providers/shared_library/provider_ort_api_init.cc +++ b/onnxruntime/core/providers/shared_library/provider_ort_api_init.cc @@ -24,7 +24,7 @@ std::once_flag init; } // namespace void InitProviderOrtApi() { - std::call_once(init, []() { Ort::Global::api_ = Provider_GetHost()->OrtGetApiBase()->GetApi(ORT_API_VERSION); }); + std::call_once(init, []() { Ort::InitApi(Provider_GetHost()->OrtGetApiBase()->GetApi(ORT_API_VERSION)); }); } -} // namespace onnxruntime \ No newline at end of file +} // namespace onnxruntime diff --git a/onnxruntime/test/autoep/library/ep_arena.h b/onnxruntime/test/autoep/library/ep_arena.h index 641f3ce3f7b17..caa2c61db835f 100644 --- a/onnxruntime/test/autoep/library/ep_arena.h +++ b/onnxruntime/test/autoep/library/ep_arena.h @@ -21,7 +21,10 @@ limitations under the License. #include #include +#define ORT_API_MANUAL_INIT #include "onnxruntime_cxx_api.h" +#undef ORT_API_MANUAL_INIT + #include "ep_allocator.h" #include "example_plugin_ep_utils.h" diff --git a/onnxruntime/test/testdata/custom_op_library/custom_op_library.cc b/onnxruntime/test/testdata/custom_op_library/custom_op_library.cc index 8ab58adbeeb74..bc22864304567 100644 --- a/onnxruntime/test/testdata/custom_op_library/custom_op_library.cc +++ b/onnxruntime/test/testdata/custom_op_library/custom_op_library.cc @@ -26,7 +26,7 @@ static void AddOrtCustomOpDomainToContainer(Ort::CustomOpDomain&& domain) { } OrtStatus* ORT_API_CALL RegisterCustomOps(OrtSessionOptions* options, const OrtApiBase* api) { - Ort::Global::api_ = api->GetApi(ORT_API_VERSION); + Ort::InitApi(api->GetApi(ORT_API_VERSION)); OrtStatus* result = nullptr; ORT_TRY { From 69717e767c5ec3cf00f302c3ec0ea5af33e6e942 Mon Sep 17 00:00:00 2001 From: Christopher Warrington Date: Wed, 13 Aug 2025 17:58:46 -0700 Subject: [PATCH 2/7] Only use #pragma detect_mismatch with MSVC Some of the Clang docs implied that detect_mismatch was supported by Clang, but the CI build produced errors about an unknown pragrma. --- include/onnxruntime/core/session/onnxruntime_cxx_api.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/onnxruntime/core/session/onnxruntime_cxx_api.h b/include/onnxruntime/core/session/onnxruntime_cxx_api.h index 272459ef97113..67e9784696c6d 100644 --- a/include/onnxruntime/core/session/onnxruntime_cxx_api.h +++ b/include/onnxruntime/core/session/onnxruntime_cxx_api.h @@ -133,7 +133,7 @@ struct Global { inline void InitApi(const OrtApi* api) noexcept { detail::Global::api_ = api; } inline void InitApi() noexcept { InitApi(OrtGetApiBase()->GetApi(ORT_API_VERSION)); } -#if defined(__clang__) || defined(_MSC_VER) +#ifdef _MSC_VER // If you get a linker error about a mismatch here, you are trying to // link two compilation units that have different definitions for // ORT_API_MANUAL_INIT together. All compilation units must agree on the @@ -149,7 +149,7 @@ inline const OrtApi* detail::Global::DefaultInit() noexcept { #else // ORT_API_MANUAL_INIT -#if defined(__clang__) || defined(_MSC_VER) +#ifdef _MSC_VER // If you get a linker error about a mismatch here, you are trying to link // two compilation units that have different definitions for // ORT_API_MANUAL_INIT together. All compilation units must agree on the From 3024cca69f49b017f0e968f8885d0d34a932a070 Mon Sep 17 00:00:00 2001 From: Christopher Warrington Date: Wed, 13 Aug 2025 17:59:12 -0700 Subject: [PATCH 3/7] Call public API from Node.js binding and Visit EP The Node.js and Vitis EPs were accessing internal implementation details for the C++ API. Switch them to use public APIs instead. --- js/node/src/inference_session_wrap.cc | 2 +- onnxruntime/core/providers/vitisai/imp/global_api.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/node/src/inference_session_wrap.cc b/js/node/src/inference_session_wrap.cc index 84ed3457a488b..8db91f792cb06 100644 --- a/js/node/src/inference_session_wrap.cc +++ b/js/node/src/inference_session_wrap.cc @@ -15,7 +15,7 @@ Napi::Object InferenceSessionWrap::Init(Napi::Env env, Napi::Object exports) { // create ONNX runtime env Ort::InitApi(); ORT_NAPI_THROW_ERROR_IF( - Ort::Global::api_ == nullptr, env, + &Ort::GetApi() == nullptr, env, "Failed to initialize ONNX Runtime API. It could happen when this nodejs binding was built with a higher version " "ONNX Runtime but now runs with a lower version ONNX Runtime DLL(or shared library)."); diff --git a/onnxruntime/core/providers/vitisai/imp/global_api.cc b/onnxruntime/core/providers/vitisai/imp/global_api.cc index 5fc0b8900730b..04f3d3a6c51ce 100644 --- a/onnxruntime/core/providers/vitisai/imp/global_api.cc +++ b/onnxruntime/core/providers/vitisai/imp/global_api.cc @@ -229,7 +229,7 @@ int vitisai_ep_set_ep_dynamic_options( struct MyCustomOpKernel : OpKernel { MyCustomOpKernel(const OpKernelInfo& info, const OrtCustomOp& op) : OpKernel(info), op_(op) { op_kernel_ = - op_.CreateKernel(&op_, Ort::Global::api_, reinterpret_cast(&info)); + op_.CreateKernel(&op_, &Ort::GetApi() _, reinterpret_cast(&info)); } ~MyCustomOpKernel() override { op_.KernelDestroy(op_kernel_); } @@ -333,7 +333,7 @@ vaip_core::OrtApiForVaip* create_org_api_hook() { set_version_info(the_global_api); the_global_api.host_ = Provider_GetHost(); assert(Ort::Global::api_ != nullptr); - the_global_api.ort_api_ = Ort::Global::api_; + the_global_api.ort_api_ = &Ort::GetApi(); the_global_api.model_load = [](const std::string& filename) -> Model* { auto model_proto = ONNX_NAMESPACE::ModelProto::Create(); auto& logger = logging::LoggingManager::DefaultLogger(); From 54c0cc604e4dd1dd0c34b08b769cb8b0faa671e2 Mon Sep 17 00:00:00 2001 From: Christopher Warrington Date: Wed, 13 Aug 2025 22:12:56 -0700 Subject: [PATCH 4/7] Apply AI suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- include/onnxruntime/core/session/onnxruntime_cxx_api.h | 2 +- onnxruntime/core/providers/vitisai/imp/global_api.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/onnxruntime/core/session/onnxruntime_cxx_api.h b/include/onnxruntime/core/session/onnxruntime_cxx_api.h index 67e9784696c6d..b6a0313fee136 100644 --- a/include/onnxruntime/core/session/onnxruntime_cxx_api.h +++ b/include/onnxruntime/core/session/onnxruntime_cxx_api.h @@ -166,7 +166,7 @@ inline const OrtApi* detail::Global::DefaultInit() noexcept { // This makes it safe to have a Ort::Env constructed as a static member. // // This DOES NOT make it safe to _use_ arbitrary ORT C++ APIs when - // initializaing static members, however. + // initializing static members, however. static const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); return api; } diff --git a/onnxruntime/core/providers/vitisai/imp/global_api.cc b/onnxruntime/core/providers/vitisai/imp/global_api.cc index 04f3d3a6c51ce..c752ee4e1ebd8 100644 --- a/onnxruntime/core/providers/vitisai/imp/global_api.cc +++ b/onnxruntime/core/providers/vitisai/imp/global_api.cc @@ -229,7 +229,7 @@ int vitisai_ep_set_ep_dynamic_options( struct MyCustomOpKernel : OpKernel { MyCustomOpKernel(const OpKernelInfo& info, const OrtCustomOp& op) : OpKernel(info), op_(op) { op_kernel_ = - op_.CreateKernel(&op_, &Ort::GetApi() _, reinterpret_cast(&info)); + op_.CreateKernel(&op_, &Ort::GetApi(), reinterpret_cast(&info)); } ~MyCustomOpKernel() override { op_.KernelDestroy(op_kernel_); } From 0ad02641c7c01b2d3d889ed6c49f557ad4c1699e Mon Sep 17 00:00:00 2001 From: Christopher Warrington Date: Mon, 18 Aug 2025 13:57:46 -0700 Subject: [PATCH 5/7] Make Global::Api's OrtApi* a function-level static --- .../core/session/onnxruntime_cxx_api.h | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/include/onnxruntime/core/session/onnxruntime_cxx_api.h b/include/onnxruntime/core/session/onnxruntime_cxx_api.h index b6a0313fee136..b91fbedddb234 100644 --- a/include/onnxruntime/core/session/onnxruntime_cxx_api.h +++ b/include/onnxruntime/core/session/onnxruntime_cxx_api.h @@ -108,17 +108,33 @@ namespace detail { // This is used internally by the C++ API. This class holds the global // variable that points to the OrtApi. struct Global { - static const OrtApi* GetApi() noexcept { - if (!api_) { - api_ = DefaultInit(); + static const OrtApi* Api(const OrtApi* newValue = nullptr) noexcept { + // This block-level static will be initialized once when this function is + // first executed, delaying the call to DefaultInit() until it is first needed. + // + // When ORT_API_MANUAL_INIT is not defined, DefaultInit() calls + // OrtGetApiBase()->GetApi(), which may result in a shared library being + // loaded. + // + // Using a block-level static instead of a class-level static helps + // avoid issues with static initialization order and dynamic libraries + // loading other dynamic libraries. + // + // This makes it safe to include the C++ API headers in a shared library + // that is delay loaded or delay loads its dependencies. + // + // This DOES NOT make it safe to _use_ arbitrary ORT C++ APIs when + // initializaing static members, however. + static const OrtApi* api = DefaultInit(); + + if (newValue) { + api = newValue; } - return api_; + return api; } private: - inline static const OrtApi* api_ = nullptr; - // Has different definitions based on ORT_API_MANUAL_INIT static const OrtApi* DefaultInit() noexcept; @@ -130,7 +146,7 @@ struct Global { #ifdef ORT_API_MANUAL_INIT -inline void InitApi(const OrtApi* api) noexcept { detail::Global::api_ = api; } +inline void InitApi(const OrtApi* api) noexcept { detail::Global::Api(api); } inline void InitApi() noexcept { InitApi(OrtGetApiBase()->GetApi(ORT_API_VERSION)); } #ifdef _MSC_VER @@ -158,22 +174,12 @@ inline const OrtApi* detail::Global::DefaultInit() noexcept { #endif inline const OrtApi* detail::Global::DefaultInit() noexcept { - // This block-level static will be initialized once when this function is - // first executed, delaying the call to OrtGetApiBase()->GetApi() until it - // is first needed. This helps avoid issues with static initialization - // order and dynamic libraries loading other dynamic libraries. - // - // This makes it safe to have a Ort::Env constructed as a static member. - // - // This DOES NOT make it safe to _use_ arbitrary ORT C++ APIs when - // initializing static members, however. - static const OrtApi* api = OrtGetApiBase()->GetApi(ORT_API_VERSION); - return api; + return OrtGetApiBase()->GetApi(ORT_API_VERSION); } #endif // ORT_API_MANUAL_INIT /// This returns a reference to the ORT C API. -inline const OrtApi& GetApi() noexcept { return *detail::Global::GetApi(); } +inline const OrtApi& GetApi() noexcept { return *detail::Global::Api(); } /// /// This function returns the onnxruntime version string From 035d0ecc531962ab365a7c5a5f7bf0925ee11941 Mon Sep 17 00:00:00 2001 From: Christopher Warrington Date: Mon, 18 Aug 2025 14:04:39 -0700 Subject: [PATCH 6/7] Hide InitApi functions unless ORT_API_MANUAL_INIT is defined --- include/onnxruntime/core/session/onnxruntime_cxx_api.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/include/onnxruntime/core/session/onnxruntime_cxx_api.h b/include/onnxruntime/core/session/onnxruntime_cxx_api.h index b91fbedddb234..272f88f480955 100644 --- a/include/onnxruntime/core/session/onnxruntime_cxx_api.h +++ b/include/onnxruntime/core/session/onnxruntime_cxx_api.h @@ -79,8 +79,10 @@ struct Exception : std::exception { throw Ort::Exception(string, code) #endif -// If macro ORT_API_MANUAL_INIT is defined, no static initialization will be -// performed. Instead, users must call InitApi() before using ORT. +#ifdef ORT_API_MANUAL_INIT +// If the macro ORT_API_MANUAL_INIT is defined, no static initialization +// will be performed. Instead, users must call InitApi() before using the +// ORT C++ APIs.. // // InitApi() sets the global API object using the default initialization // logic. Users call this to initialize the ORT C++ APIs at a time that @@ -103,6 +105,7 @@ inline void InitApi() noexcept; // } // inline void InitApi(const OrtApi* api) noexcept; +#endif namespace detail { // This is used internally by the C++ API. This class holds the global @@ -138,14 +141,17 @@ struct Global { // Has different definitions based on ORT_API_MANUAL_INIT static const OrtApi* DefaultInit() noexcept; +#ifdef ORT_API_MANUAL_INIT // Public APIs to set the OrtApi* to use. friend void ::Ort::InitApi() noexcept; friend void ::Ort::InitApi(const OrtApi*) noexcept; +#endif }; } // namespace detail #ifdef ORT_API_MANUAL_INIT +// See comments on declaration above for usage. inline void InitApi(const OrtApi* api) noexcept { detail::Global::Api(api); } inline void InitApi() noexcept { InitApi(OrtGetApiBase()->GetApi(ORT_API_VERSION)); } From 56ebfb50be3b1c4bb2af7922c4a4d101cd97c97c Mon Sep 17 00:00:00 2001 From: Christopher Warrington Date: Mon, 25 Aug 2025 18:38:18 -0700 Subject: [PATCH 7/7] Minor cleanups * Fix spelling error * Fix assert code that doesn't appear to ever be compiled. --- include/onnxruntime/core/session/onnxruntime_cxx_api.h | 2 +- onnxruntime/core/providers/vitisai/imp/global_api.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/onnxruntime/core/session/onnxruntime_cxx_api.h b/include/onnxruntime/core/session/onnxruntime_cxx_api.h index 272f88f480955..4632dfbb10167 100644 --- a/include/onnxruntime/core/session/onnxruntime_cxx_api.h +++ b/include/onnxruntime/core/session/onnxruntime_cxx_api.h @@ -127,7 +127,7 @@ struct Global { // that is delay loaded or delay loads its dependencies. // // This DOES NOT make it safe to _use_ arbitrary ORT C++ APIs when - // initializaing static members, however. + // initializing static members, however. static const OrtApi* api = DefaultInit(); if (newValue) { diff --git a/onnxruntime/core/providers/vitisai/imp/global_api.cc b/onnxruntime/core/providers/vitisai/imp/global_api.cc index c752ee4e1ebd8..580fbfbdba0b0 100644 --- a/onnxruntime/core/providers/vitisai/imp/global_api.cc +++ b/onnxruntime/core/providers/vitisai/imp/global_api.cc @@ -332,7 +332,7 @@ vaip_core::OrtApiForVaip* create_org_api_hook() { InitProviderOrtApi(); set_version_info(the_global_api); the_global_api.host_ = Provider_GetHost(); - assert(Ort::Global::api_ != nullptr); + assert(&Ort::GetApi() != nullptr); the_global_api.ort_api_ = &Ort::GetApi(); the_global_api.model_load = [](const std::string& filename) -> Model* { auto model_proto = ONNX_NAMESPACE::ModelProto::Create();