software-mansion / software-mansion/react-native-executorch
libexecutorch.so re-exports its own libc++, duplicating 91% of libc++_shared's ABI
- Dominant language
- TypeScript
- Stars
- 1.7k
- Forks
- 96
- Avg merge
- 20h 51m
- Merged PRs (30d)
- 59
Description
Summary
libexecutorch.so statically links the NDK libc++ and re-exports it. It exports 12 115 symbols; 2 130 of them are also exported by libc++_shared.so — 91% of that library's entire surface.
| duplicated | count |
|---|---|
__cxa_* |
29 |
typeinfo / typestring (_ZTI, _ZTS) |
448 |
_ZNSt (std::) |
1 007 |
| total | 2 130 |
Both use the __ndk1 inline namespace, so they are the same NDK libc++ flavour, and every duplicate is GLOBAL with DEFAULT visibility.
libRnExecutorch.so imports __cxa_throw and __cxa_begin_catch, and its DT_NEEDED lists libexecutorch_jni.so (position 3) before libc++_shared.so (position 11) — both at depth 1 — so RNE's C++ runtime is in practice the copy inside libexecutorch.so. (libexecutorch.so's SONAME is libexecutorch_jni.so, which is how that entry resolves to a file of a different name.)
This is not currently broken
Because every duplicate is GLOBAL/DEFAULT, ELF symbol interposition applies: the first definition in the lookup scope wins and every library binds to that same copy. One effective __cxa_throw, one effective _ZTISt9exception per process, so typeinfo pointer comparison stays consistent and catch blocks match. That is why RNE ships and works.
It is also not a size problem: the 2 130 duplicated symbols occupy 277 058 B (0.26 MB) of libexecutorch.so's 12.82 MB. Most of libc++'s weight is header-inlined templates, not these exported entry points.
What would break it
- NDK version skew. If the libc++ statically linked into
libexecutorch.soand thelibc++_shared.soan app ships ever come from different NDKs, interposition silently mixes two layouts of the same types. They match today; nothing enforces that. - Anything that defeats interposition — a second linker namespace, or a library given a private lookup scope — puts two
__cxa_eh_globalsand two typeinfo sets in one process. An exception thrown against one runtime and caught against the other does not match, and terminates.
Fix
Build ExecuTorch's Android target against c++_shared rather than static libc++, so libexecutorch.so stops carrying and exporting its own copy. Needs a rebuild of the fork and a republish of core-android-*, so it cannot ride on the v0.10.1-libs repackage (which deliberately recompiled nothing).
Reproduction
NDK=$ANDROID_HOME/ndk/<ver>/toolchains/llvm/prebuilt/darwin-x86_64/bin
$NDK/llvm-nm -D --defined-only libexecutorch.so | awk '{print $3}' | sort -u > et.syms
$NDK/llvm-nm -D --defined-only libc++_shared.so | awk '{print $3}' | sort -u > cxx.syms
comm -12 et.syms cxx.syms | wc -l # 2130
$NDK/llvm-readelf --dyn-syms libexecutorch.so | grep -E ' __cxa_throw$| _ZTISt9exception$'
$NDK/llvm-readelf -d libRnExecutorch.so | grep NEEDED
Found while assessing §11 of #1464.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.