fix(executorch): share the C++ runtime instead of shipping a second copy - #4592
Open
shoumikhin wants to merge 1 commit into
Open
fix(executorch): share the C++ runtime instead of shipping a second copy#4592shoumikhin wants to merge 1 commit into
shoumikhin wants to merge 1 commit into
Conversation
shoumikhin
force-pushed
the
fix/et-runtime-shared-cxx-runtime
branch
from
August 26, 2026 15:31
656883f to
2659c08
Compare
The prebuilt ExecuTorch runtime wheel statically links libstdc++ into every shared object it ships, and loading it segfaults. _portable_lib.so lists libaoti_cuda_shims.so as its first DT_NEEDED, and no object in the wheel declares libstdc++.so.6, so the shim leads the dlopen group in symbol search order and everything loaded alongside it resolves the C++ runtime against the wheel. Measured with LD_DEBUG=bindings on a built wheel, libnvinfer.so.11 resolves 110 symbols into libaoti_cuda_shims.so and 22 into _portable_lib.so, against 6 into libstdc++.so.6. Among them are the __cxxabiv1 type_info vtables and the std::locale internals, which have to be unique in a process. Two libstdc++ builds then share one process. A locale facet built by one gets indexed with the other's std::locale::id, so a virtual call lands on the wrong slot and stores through a garbage pointer. Link the C++ runtime dynamically instead. libtorch_cpu.so, libc10.so, libtorch_python.so and libnvinfer.so.11 all already carry DT_NEEDED libstdc++.so.6 and need no more than GLIBCXX_3.4.22, so a shared libstdc++ is already in any process that can load this wheel, and the second copy protects against nothing. Hiding the static copy is not an alternative: private __cxxabiv1 type_info stops catch (std::exception&) from matching across a library boundary and makes dynamic_cast return null, which trades a crash for silent wrong answers. The placement analysis stays as it was. The toolchain hands CMake the C driver, which links no C++ runtime, and its injected -lstdc++ sits ahead of the objects wrapped in --as-needed, where it resolves nothing. That fragment still has to be stripped. Only what replaces it changes, from libstdc++.a after the objects to -lstdc++ after the objects. The build check is inverted to match. It now fails when a shipped object defines libstdc++'s own symbols, and when an object references the C++ runtime without declaring where it comes from, which is the missing exception_ptr::_M_addref the old check was written for.
shoumikhin
force-pushed
the
fix/et-runtime-shared-cxx-runtime
branch
from
August 26, 2026 18:00
2659c08 to
8c2bf73
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Depends on #4581, which is included here so CI can reach the crash.
executorch-runtime-testis gated onexecutorch-runtime-build, and that build fails on main today, so the test is skipped rather than red and this crash never shows up. Rebase drops the first two commits once #4581 lands.Description
The prebuilt ExecuTorch runtime wheel statically links libstdc++ into every shared object it ships. Loading it segfaults.
_portable_lib.solistslibaoti_cuda_shims.soas its firstDT_NEEDED, and no object in the wheel declareslibstdc++.so.6, so the shim leads the dlopen group in symbol search order and everything loaded alongside it resolves the C++ runtime against the wheel. Measured withLD_DEBUG=bindingson a built wheel,libnvinfer.so.11resolves 110 symbols intolibaoti_cuda_shims.soand 22 into_portable_lib.so, against 6 intolibstdc++.so.6. Among them are the__cxxabiv1type_info vtables and thestd::localeinternals, which have to be unique in a process.Two libstdc++ builds then share one process. A locale facet built by one gets indexed with the other's
std::locale::id, so a virtual call lands on the wrong slot and stores through a garbage pointer. The crash lands in the shim's ownstd::codecvt<char16_t, char, mbstate_t>::do_unshift, four bytes in, on a store through the fifth argument register.This is why
executorch-runtime-testexits 139 while probing the extension. The job is gated onexecutorch-runtime-build, which has been failing, so it has been skipped rather than red and the crash was never visible.The fix
Link the C++ runtime dynamically.
libtorch_cpu.so,libc10.so,libtorch_python.soandlibnvinfer.so.11all already carryDT_NEEDED libstdc++.so.6and need no more thanGLIBCXX_3.4.22, so a shared libstdc++ is already present in any process that can load this wheel, and the second copy protects against nothing.Hiding the static copy is not an alternative. Private
__cxxabiv1type_info stopscatch (std::exception&)from matching across a library boundary and makesdynamic_castreturn null, which trades a crash for silent wrong answers. A private C++ runtime is only sound in a shared object whose whole interface is C, and_portable_lib.sohandsat::Tensorandstd::stringacross.The placement analysis in the CMake file stays as it was. The toolchain hands CMake the C driver, which links no C++ runtime, and its injected
-lstdc++sits ahead of the object files wrapped in--as-needed, where it resolves nothing. That fragment still has to be stripped. Only what replaces it changes, fromlibstdc++.aafter the objects to-lstdc++after the objects.The build check is inverted to match. It now fails when a shipped object defines libstdc++'s own symbols, and when an object references the C++ runtime without declaring where it comes from, which is the missing
exception_ptr::_M_addrefthe old check was written for.Type of change
Checklist: