Skip to content

Commit 48f882f

Browse files
chwarrCopilot
authored andcommitted
[c++] Eliminate dynamic initialization of static Ort::Global<void>::api_ (microsoft#25741)
### Description Delay the call to `OrtGetApiBase()` until the first call to `Ort::GetApi()` so that `OrtGetApiBase()` is typically called after dynamic library loading. ### Motivation and Context When ORT_API_MANUAL_INIT is not defined (which is the default), the static `Ort::Global<void>::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. * Replace the templated `Global<void>::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<void>` has been replaced with a non-templated type and moved inside a `detail` namespace. Since the `Global<void>` 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. (MSVC only.) --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 94f76b9 commit 48f882f

File tree

6 files changed

+99
-33
lines changed

6 files changed

+99
-33
lines changed

include/onnxruntime/core/session/onnxruntime_cxx_api.h

Lines changed: 89 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -79,22 +79,19 @@ struct Exception : std::exception {
7979
throw Ort::Exception(string, code)
8080
#endif
8181

82-
// This is used internally by the C++ API. This class holds the global variable that points to the OrtApi,
83-
// it's in a template so that we can define a global variable in a header and make
84-
// it transparent to the users of the API.
85-
template <typename T>
86-
struct Global {
87-
static const OrtApi* api_;
88-
};
89-
90-
// If macro ORT_API_MANUAL_INIT is defined, no static initialization will be performed. Instead, user must call InitApi() before using it.
91-
template <typename T>
9282
#ifdef ORT_API_MANUAL_INIT
93-
const OrtApi* Global<T>::api_{};
94-
inline void InitApi() noexcept { Global<void>::api_ = OrtGetApiBase()->GetApi(ORT_API_VERSION); }
95-
96-
// Used by custom operator libraries that are not linked to onnxruntime. Sets the global API object, which is
97-
// required by C++ APIs.
83+
// If the macro ORT_API_MANUAL_INIT is defined, no static initialization
84+
// will be performed. Instead, users must call InitApi() before using the
85+
// ORT C++ APIs..
86+
//
87+
// InitApi() sets the global API object using the default initialization
88+
// logic. Users call this to initialize the ORT C++ APIs at a time that
89+
// makes sense in their program.
90+
inline void InitApi() noexcept;
91+
92+
// InitApi(const OrtApi*) is used by custom operator libraries that are not
93+
// linked to onnxruntime. It sets the global API object, which is required
94+
// by the ORT C++ APIs.
9895
//
9996
// Example mycustomop.cc:
10097
//
@@ -107,22 +104,88 @@ inline void InitApi() noexcept { Global<void>::api_ = OrtGetApiBase()->GetApi(OR
107104
// // ...
108105
// }
109106
//
110-
inline void InitApi(const OrtApi* api) noexcept { Global<void>::api_ = api; }
111-
#else
112-
#if defined(_MSC_VER) && !defined(__clang__)
113-
#pragma warning(push)
114-
// "Global initializer calls a non-constexpr function." Therefore you can't use ORT APIs in the other global initializers.
115-
// Please define ORT_API_MANUAL_INIT if it conerns you.
116-
#pragma warning(disable : 26426)
107+
inline void InitApi(const OrtApi* api) noexcept;
117108
#endif
118-
const OrtApi* Global<T>::api_ = OrtGetApiBase()->GetApi(ORT_API_VERSION);
119-
#if defined(_MSC_VER) && !defined(__clang__)
120-
#pragma warning(pop)
109+
110+
namespace detail {
111+
// This is used internally by the C++ API. This class holds the global
112+
// variable that points to the OrtApi.
113+
struct Global {
114+
static const OrtApi* Api(const OrtApi* newValue = nullptr) noexcept {
115+
// This block-level static will be initialized once when this function is
116+
// first executed, delaying the call to DefaultInit() until it is first needed.
117+
//
118+
// When ORT_API_MANUAL_INIT is not defined, DefaultInit() calls
119+
// OrtGetApiBase()->GetApi(), which may result in a shared library being
120+
// loaded.
121+
//
122+
// Using a block-level static instead of a class-level static helps
123+
// avoid issues with static initialization order and dynamic libraries
124+
// loading other dynamic libraries.
125+
//
126+
// This makes it safe to include the C++ API headers in a shared library
127+
// that is delay loaded or delay loads its dependencies.
128+
//
129+
// This DOES NOT make it safe to _use_ arbitrary ORT C++ APIs when
130+
// initializing static members, however.
131+
static const OrtApi* api = DefaultInit();
132+
133+
if (newValue) {
134+
api = newValue;
135+
}
136+
137+
return api;
138+
}
139+
140+
private:
141+
// Has different definitions based on ORT_API_MANUAL_INIT
142+
static const OrtApi* DefaultInit() noexcept;
143+
144+
#ifdef ORT_API_MANUAL_INIT
145+
// Public APIs to set the OrtApi* to use.
146+
friend void ::Ort::InitApi() noexcept;
147+
friend void ::Ort::InitApi(const OrtApi*) noexcept;
121148
#endif
149+
};
150+
} // namespace detail
151+
152+
#ifdef ORT_API_MANUAL_INIT
153+
154+
// See comments on declaration above for usage.
155+
inline void InitApi(const OrtApi* api) noexcept { detail::Global::Api(api); }
156+
inline void InitApi() noexcept { InitApi(OrtGetApiBase()->GetApi(ORT_API_VERSION)); }
157+
158+
#ifdef _MSC_VER
159+
// If you get a linker error about a mismatch here, you are trying to
160+
// link two compilation units that have different definitions for
161+
// ORT_API_MANUAL_INIT together. All compilation units must agree on the
162+
// definition of ORT_API_MANUAL_INIT.
163+
#pragma detect_mismatch("ORT_API_MANUAL_INIT", "enabled")
164+
#endif
165+
166+
inline const OrtApi* detail::Global::DefaultInit() noexcept {
167+
// When ORT_API_MANUAL_INIT is defined, there's no default init that can
168+
// be done.
169+
return nullptr;
170+
}
171+
172+
#else // ORT_API_MANUAL_INIT
173+
174+
#ifdef _MSC_VER
175+
// If you get a linker error about a mismatch here, you are trying to link
176+
// two compilation units that have different definitions for
177+
// ORT_API_MANUAL_INIT together. All compilation units must agree on the
178+
// definition of ORT_API_MANUAL_INIT.
179+
#pragma detect_mismatch("ORT_API_MANUAL_INIT", "disabled")
122180
#endif
123181

182+
inline const OrtApi* detail::Global::DefaultInit() noexcept {
183+
return OrtGetApiBase()->GetApi(ORT_API_VERSION);
184+
}
185+
#endif // ORT_API_MANUAL_INIT
186+
124187
/// This returns a reference to the ORT C API.
125-
inline const OrtApi& GetApi() noexcept { return *Global<void>::api_; }
188+
inline const OrtApi& GetApi() noexcept { return *detail::Global::Api(); }
126189

127190
/// <summary>
128191
/// This function returns the onnxruntime version string

js/node/src/inference_session_wrap.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Napi::Object InferenceSessionWrap::Init(Napi::Env env, Napi::Object exports) {
1515
// create ONNX runtime env
1616
Ort::InitApi();
1717
ORT_NAPI_THROW_ERROR_IF(
18-
Ort::Global<void>::api_ == nullptr, env,
18+
&Ort::GetApi() == nullptr, env,
1919
"Failed to initialize ONNX Runtime API. It could happen when this nodejs binding was built with a higher version "
2020
"ONNX Runtime but now runs with a lower version ONNX Runtime DLL(or shared library).");
2121

onnxruntime/core/providers/shared_library/provider_ort_api_init.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ std::once_flag init;
2424
} // namespace
2525

2626
void InitProviderOrtApi() {
27-
std::call_once(init, []() { Ort::Global<void>::api_ = Provider_GetHost()->OrtGetApiBase()->GetApi(ORT_API_VERSION); });
27+
std::call_once(init, []() { Ort::InitApi(Provider_GetHost()->OrtGetApiBase()->GetApi(ORT_API_VERSION)); });
2828
}
2929

30-
} // namespace onnxruntime
30+
} // namespace onnxruntime

onnxruntime/core/providers/vitisai/imp/global_api.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ int vitisai_ep_set_ep_dynamic_options(
229229
struct MyCustomOpKernel : OpKernel {
230230
MyCustomOpKernel(const OpKernelInfo& info, const OrtCustomOp& op) : OpKernel(info), op_(op) {
231231
op_kernel_ =
232-
op_.CreateKernel(&op_, Ort::Global<void>::api_, reinterpret_cast<const OrtKernelInfo*>(&info));
232+
op_.CreateKernel(&op_, &Ort::GetApi(), reinterpret_cast<const OrtKernelInfo*>(&info));
233233
}
234234

235235
~MyCustomOpKernel() override { op_.KernelDestroy(op_kernel_); }
@@ -332,8 +332,8 @@ vaip_core::OrtApiForVaip* create_org_api_hook() {
332332
InitProviderOrtApi();
333333
set_version_info(the_global_api);
334334
the_global_api.host_ = Provider_GetHost();
335-
assert(Ort::Global<void>::api_ != nullptr);
336-
the_global_api.ort_api_ = Ort::Global<void>::api_;
335+
assert(&Ort::GetApi() != nullptr);
336+
the_global_api.ort_api_ = &Ort::GetApi();
337337
the_global_api.model_load = [](const std::string& filename) -> Model* {
338338
auto model_proto = ONNX_NAMESPACE::ModelProto::Create();
339339
auto& logger = logging::LoggingManager::DefaultLogger();

onnxruntime/test/autoep/library/ep_arena.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ limitations under the License.
2121
#include <mutex>
2222
#include <set>
2323

24+
#define ORT_API_MANUAL_INIT
2425
#include "onnxruntime_cxx_api.h"
26+
#undef ORT_API_MANUAL_INIT
27+
2528
#include "ep_allocator.h"
2629
#include "example_plugin_ep_utils.h"
2730

onnxruntime/test/testdata/custom_op_library/custom_op_library.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ static void AddOrtCustomOpDomainToContainer(Ort::CustomOpDomain&& domain) {
2626
}
2727

2828
OrtStatus* ORT_API_CALL RegisterCustomOps(OrtSessionOptions* options, const OrtApiBase* api) {
29-
Ort::Global<void>::api_ = api->GetApi(ORT_API_VERSION);
29+
Ort::InitApi(api->GetApi(ORT_API_VERSION));
3030
OrtStatus* result = nullptr;
3131

3232
ORT_TRY {

0 commit comments

Comments
 (0)