Simdutf Can Now Be Used Without libc++ or libc++abi – Mitchell Hashimoto<br>Mitchell Hashimoto
Mitchell Hashimoto<br>Simdutf Can Now Be Used Without libc++ or libc++abi<br>April 15, 2026<br>As of this PR,<br>simdutf<br>can be used without libc++ or libc++abi1.
Simdutf was the final remaining libc++ dependency in<br>libghostty-vt2.<br>After updating Ghostty to use this new simdutf build,<br>we were able to remove libc++ and libc++abi completely from our dependencies.
The Benefits of Not Depending on libc++
Not depending on libc++ makes a library more portable (embedded, WebAssembly,<br>freestanding environments), simplifies cross-compilation (no target-specific<br>C++ standard library needed), reduces binary size, and can simplify<br>static linking.
As a general purpose, low-level library, simdutf should be as portable and<br>flexible as possible. If a downstream consumer can easily use libc++,<br>great! But if they can't, they shouldn't be blocked from using simdutf<br>since it fundamentally doesn't need it.
libc++ vs libc++abi
There are two parts to extricating a program from libc++.
First, libc++ is the C++ standard library, which provides things like<br>std::vector, std::string, etc. If you import or even the libc<br>C++ fallbacks like , you're depending on libc++.
The sneakier part is libc++abi, which provides the C++ ABI, including<br>things like exception handling, virtual function tables, RTTI, etc.<br>If you use any C++ features that require these, you're depending on libc++abi,<br>even if you don't import any C++ standard library headers at all.
For example, the minimal C++ program below depends on libc++abi<br>because it uses function-local static variables, which require thread-safe<br>initialization that is part of the C++ ABI:
struct Implementation {<br>int version;<br>};
const Implementation& get_impl() {<br>static const Implementation impl{1};<br>return impl;
int main() {<br>return get_impl().version;
Extricating simdutf from libc++
Let's talk about libc++ first (not the ABI).
simdutf is a C++ library that makes heavy use of the latest and greatest C++ features and though<br>I don't know him personally, Daniel Lemire seems<br>to really love utilizing C++ features to their fullest extent. So, simdutf is<br>very much a C++ project.
For there to be any chance that my changes would be accepted, I had to make<br>sure that the project could continue to use C++ features without it<br>being a headache.
STL Usage
The approach I decided to take was to introduce a<br>stl_compat.h header<br>that centralizes all of the C++ standard library types. In<br>normal libc++ mode, everything in stl_compat.h is a simple include or<br>minimal alias to the corresponding C++ standard library type with no runtime<br>overhead.
In NO_LIBCXX mode, stl_compat.h provides its own implementations of the<br>C++ types that simdutf uses, but only just enough for them to be compatible<br>with what simdutf needs. For example, stl_compat.h provides its own<br>implementation of std::pair.
As a result, the changes required end up mostly looking like this<br>throughout the diff:
-std::pair<br>+internal::pair<br>arm_convert_latin1_to_utf32(const char *buf, size_t len,<br>char32_t *utf32_output) {
ABI Compatibility
My goal was to retain as much ABI compatibility as possible.<br>Some of the public ABI<br>expose C++ types such as std::string, so in those scenarios, ABI<br>had to be broken. Otherwise, it is fully retained.
Given SIMDUTF_NO_LIBCXX is a new feature that applies changes<br>to the compilation unit, I felt that it was acceptable to break ABI<br>only when this flag is present. In the existing case where the<br>NO_LIBCXX flag is not present, the ABI is fully retained and simdutf can be<br>updated without breaking ABI for existing users.
I was pleasantly surprised to find that the ABI breakage was very minimal,<br>and only applied to a handful of diagnostic functions (e.g. to get<br>the name of the active implementation) and helpers to work with other<br>C++ types (e.g. text encoding std::string). Since by definition someone<br>using SIMDUTF_NO_LIBCXX is not interested in libc++, these ABI<br>breakages felt like a feature rather than a bug.
Extricating simdutf from libc++abi
This task was significantly more complex.
The main problem was that libc++abi dependencies don't usually show up as<br>obvious source-level includes. They show up because the compiler quietly emits<br>calls into the C++ ABI runtime for ordinary-looking language features.<br>To detect these, I had to write a script that decompiles the object files<br>and looks for symbols such as __cxa_guard_acquire.
The biggest offender in simdutf was the runtime dispatch layer. The original<br>code relied heavily on function-local static variables. In C++, those locals<br>are guarded by thread-safe initialization helpers such as __cxa_guard_acquire<br>and __cxa_guard_release, which are provided by the C++ ABI runtime. So even<br>though the code didn't mention libc++abi anywhere, the compiled object still depended on it.<br>The fix for this is to use translation-unit static variables instead of<br>function-local statics in NO_LIBCXX...