Building against the sysroot fixes the glibc requirement, but the library still needs GLIBCXX_3.4.26 and CXXABI_1.3.11, against a baseline of GLIBCXX_3.4.19 and CXXABI_1.3.7. That would still lose the native backend on anything older than libstdc++ from gcc 9, which includes Debian 10 and RHEL 8. Most of that could be shimmed the way build/unix/stdc++compat does it, which already handles _Sp_make_shared_tag::_S_eq and the stringstream constructors at that exact version, and aligned new is the same shape as the operator delete(void*, size_t) shim there. std::filesystem is the exception: it isn't a template, so the explicit instantiation trick doesn't apply, its members are out of line in libstdc++.so, and shimming them means replicating path::_List and _Impl exactly. The sysroot's libstdc++fs.a is no help, since gcc 9 moved the C++17 filesystem into libstdc++.so and left it holding only std::experimental::filesystem. Going that way would also mean porting a Firefox-internal compat layer, and its mfbt dependency, into a third-party CMake build and keeping the two in sync. So link libstdc++ statically instead. That is safe here because upstream's version script exports only OrtGetApiBase and OrtSessionOptionsAppendExecutionProvider_CPU, the interface Firefox uses is the ORT C API, and the library is dlopened with PR_LD_LOCAL, so no C++ symbols or objects cross into Gecko. The library no longer links against libstdc++.so.6 at all. A static libstdc++ does bring in its __cxa_thread_atexit, a bare forward to glibc's __cxa_thread_atexit_impl, which is the only GLIBC_2.18 symbol. The stdc++compat shim can't help there: it forwards to __cxa_thread_atexit, which against a static libstdc++ is that same forward calling back into it. So provide a real implementation, the same pthread key approach libstdc++ itself uses when glibc has no __cxa_thread_atexit_impl. The library ends up at GLIBC_2.16, with no GLIBCXX_ or CXXABI_ dependency at all. The library grows by 545KB unstripped, which is not measurable in the packaged size: linux64 target.tar.xz went from 94418972 to 94366924 bytes. Differential Revision: https://phabricator.services.mozilla.com/D320001
87 lines
2.9 KiB
C++
87 lines
2.9 KiB
C++
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/* onnxruntime links libstdc++ statically, which pulls in its
|
|
* __cxa_thread_atexit. That is a bare forward to glibc's
|
|
* __cxa_thread_atexit_impl, the only symbol in GLIBC_2.18, so the library ends
|
|
* up needing a newer glibc than the 2.17 Firefox supports.
|
|
*
|
|
* build/unix/stdc++compat/stdc++compat.cpp solves this for Firefox's own
|
|
* binaries by defining __cxa_thread_atexit_impl as a forward to libstdc++'s
|
|
* __cxa_thread_atexit. That only works against a shared libstdc++, whose
|
|
* __cxa_thread_atexit has its own fallback implementation; against a static one
|
|
* the forward would call straight back into here.
|
|
*
|
|
* So provide a real implementation instead, taking the same approach libstdc++
|
|
* itself does when glibc has no __cxa_thread_atexit_impl: keep a per-thread
|
|
* stack of destructors and run it from a pthread key destructor.
|
|
*
|
|
* libstdc++ also registers that with atexit, so that the main thread's
|
|
* destructors run too, as pthread key destructors are not called when it
|
|
* returns from main or calls exit. This deliberately doesn't: running
|
|
* destructors during shutdown is riskier than leaving them, and it doesn't
|
|
* matter here, since onnxruntime is only ever called into from worker threads
|
|
* and the process is going away regardless. */
|
|
|
|
#include <pthread.h>
|
|
#include <stdlib.h>
|
|
|
|
namespace {
|
|
|
|
struct DtorEntry {
|
|
void (*mDtor)(void*);
|
|
void* mObject;
|
|
DtorEntry* mNext;
|
|
};
|
|
|
|
/* __thread rather than thread_local: this must not itself need thread_local
|
|
* destructor support, or registering a destructor would recurse. */
|
|
__thread DtorEntry* sDtors = nullptr;
|
|
|
|
pthread_key_t sKey;
|
|
pthread_once_t sKeyOnce = PTHREAD_ONCE_INIT;
|
|
|
|
extern "C" void RunDtors(void*) {
|
|
/* A destructor may register more destructors, so drain until empty rather
|
|
* than walking the list that was present on entry. */
|
|
while (sDtors) {
|
|
DtorEntry* entry = sDtors;
|
|
sDtors = entry->mNext;
|
|
entry->mDtor(entry->mObject);
|
|
free(entry);
|
|
}
|
|
}
|
|
|
|
void CreateKey() { pthread_key_create(&sKey, RunDtors); }
|
|
|
|
} // namespace
|
|
|
|
extern "C" __attribute__((visibility("hidden"))) int __cxa_thread_atexit_impl(
|
|
void (*aDtor)(void*), void* aObject, void* aDsoHandle) {
|
|
(void)aDsoHandle;
|
|
|
|
if (pthread_once(&sKeyOnce, CreateKey) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
DtorEntry* entry = static_cast<DtorEntry*>(malloc(sizeof(DtorEntry)));
|
|
if (!entry) {
|
|
return -1;
|
|
}
|
|
|
|
/* The value is never read, it only has to be non-null for the key destructor
|
|
* to be called when the thread exits. */
|
|
if (pthread_setspecific(sKey, reinterpret_cast<void*>(1)) != 0) {
|
|
free(entry);
|
|
return -1;
|
|
}
|
|
|
|
/* Destructors run in reverse order of registration, so push to the front. */
|
|
entry->mDtor = aDtor;
|
|
entry->mObject = aObject;
|
|
entry->mNext = sDtors;
|
|
sDtors = entry;
|
|
return 0;
|
|
}
|