Files
sousa-gecko/taskcluster/scripts/misc/onnxruntime-thread-atexit.cpp
Julien Cristau 8cf01d48fd Bug 2064737 - Link libstdc++ statically into the onnxruntime library. r=firefox-build-system-reviewers,sergesanspaille
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
2026-09-04 11:48:02 +00:00

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;
}