Simdutf Can Now Be Used Without libc++ or libc++abi

Mitchell Hashimoto Tools

Summary

Simdutf, a fast Unicode library, can now be used without requiring libc++ or libc++abi, making it more portable for embedded, WebAssembly, and freestanding environments. This change removes the last libc++ dependency from the Ghostty terminal emulator's library.

No content available
Original Article
View Cached Full Text

Cached at: 05/16/26, 03:37 AM

# Simdutf Can Now Be Used Without libc++ or libc++abi Source: [https://mitchellh.com/writing/simdutf-no-libcxx](https://mitchellh.com/writing/simdutf-no-libcxx) As of[this PR](https://github.com/simdutf/simdutf/pull/959#top),[simdutf](https://github.com/simdutf/simdutf)can be used without libc\+\+ or libc\+\+abi[1](https://mitchellh.com/writing/simdutf-no-libcxx#user-content-fn-1)\. Simdutf was the final remaining libc\+\+ dependency in[libghostty\-vt](https://libghostty.tip.ghostty.org/)[2](https://mitchellh.com/writing/simdutf-no-libcxx#user-content-fn-2)\. After[updating Ghostty to use this new simdutf build](https://github.com/ghostty-org/ghostty/pull/12291), 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, freestanding environments\), simplifies cross\-compilation \(no target\-specific C\+\+ standard library needed\), reduces binary size, and can simplify static linking\. As a general purpose, low\-level library, simdutf should be as portable and flexible as possible\. If a downstream consumer can easily use libc\+\+, great\! But if they can't, they shouldn't be blocked from using simdutf 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`std::vector`,`std::string`, etc\. If you import`<vector\>`or even the libc C\+\+ fallbacks like`<cstring\>`, you're depending on libc\+\+\. The sneakier part is`libc\+\+abi`, which provides the C\+\+ ABI, including things like exception handling, virtual function tables, RTTI, etc\. If you use any C\+\+ features that require these, you're depending on libc\+\+abi, even if you don't import any C\+\+ standard library headers*at all*\. For example, the minimal C\+\+ program below depends on libc\+\+abi because it uses function\-local static variables, which require thread\-safe initialization that is part of the C\+\+ ABI: ``` struct Implementation { int version; }; const Implementation& get_impl() { static const Implementation impl{1}; return impl; } int main() { 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 I don't know him personally,[Daniel Lemire](https://lemire.me/en/)seems to really love utilizing C\+\+ features to their fullest extent\. So, simdutf is very much a C\+\+ project\. For there to be any chance that my changes would be accepted, I had to make sure that the project could continue to use C\+\+ features without it being a headache\. ### STL Usage The approach I decided to take was to introduce a[`stl\_compat\.h`header](https://github.com/mitchellh/simdutf/blob/nolibcxx/include/simdutf/internal/stl_compat.h)that centralizes all of the C\+\+ standard library types\. In normal libc\+\+ mode, everything in`stl\_compat\.h`is a simple include or minimal alias to the corresponding C\+\+ standard library type with no runtime overhead\. In`NO\_LIBCXX`mode,`stl\_compat\.h`provides its own implementations of the C\+\+ types that simdutf uses, but only just enough for them to be compatible with what simdutf needs\. For example,`stl\_compat\.h`provides its own implementation of`std::pair`\. As a result, the changes required end up mostly looking like this throughout the diff: ``` -std::pair<const char *, char32_t *> +internal::pair<const char *, char32_t *> arm_convert_latin1_to_utf32(const char *buf, size_t len, char32_t *utf32_output) { ``` ### ABI Compatibility My goal was to retain as much ABI compatibility as possible\. Some of the[public ABI](https://en.wikipedia.org/wiki/Application_binary_interface)expose C\+\+ types such as`std::string`, so in those scenarios, ABI had to be broken\. Otherwise, it is fully retained\. Given`SIMDUTF\_NO\_LIBCXX`is a new feature that applies changes to the compilation unit, I felt that it was acceptable to break ABI*only when this flag is present*\. In the existing case where the`NO\_LIBCXX`flag is not present, the ABI is fully retained and simdutf can be updated without breaking ABI for existing users\. I was pleasantly surprised to find that the ABI breakage was very minimal, and only applied to a handful of diagnostic functions \(e\.g\. to get the name of the active implementation\) and helpers to work with other C\+\+ types \(e\.g\. text encoding`std::string`\)\. Since by definition someone using`SIMDUTF\_NO\_LIBCXX`is not interested in libc\+\+, these ABI 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 obvious source\-level includes\. They show up because the compiler quietly emits calls into the C\+\+ ABI runtime for ordinary\-looking language features\. To detect these, I had to write a script that decompiles the object files and looks for symbols such as`\_\_cxa\_guard\_acquire`\. The biggest offender in simdutf was the runtime dispatch layer\. The original code relied heavily on function\-local static variables\. In C\+\+, those locals are guarded by thread\-safe initialization helpers such as`\_\_cxa\_guard\_acquire`and`\_\_cxa\_guard\_release`, which are provided by the C\+\+ ABI runtime\. So even though the code didn't mention libc\+\+abi anywhere, the compiled object still depended on it\. The fix for this is to use translation\-unit static variables instead of function\-local statics in`NO\_LIBCXX`mode\. ``` #if SIMDUTF_IMPLEMENTATION_ICELAKE #ifdef SIMDUTF_NO_LIBCXX static const icelake::implementation icelake_singleton{}; #endif static const icelake::implementation *get_icelake_singleton() { #ifdef SIMDUTF_NO_LIBCXX return &icelake_singleton; #else static const icelake::implementation icelake_singleton{}; return &icelake_singleton; #endif } #endif ``` Next, simdutf models each backend as a subclass of an abstract`implementation`interface\. That design can stay, but abstract\-class vtables still reference`\_\_cxa\_pure\_virtual`for impossible\-to\-call pure virtual entries\. In`SIMDUTF\_NO\_LIBCXX`mode I chose to provide a tiny local shim and ensure the runtime never actually reaches it\. I marked this as weak so that the symbol can be overridden by the C\+\+ ABI if it is present\. ``` #ifdef SIMDUTF_NO_LIBCXX // The abstract implementation vtable still carries pure-virtual slots even // though correct dispatch never reaches them in this build mode. Provide the // narrowest possible ABI shim so stricter no-libcxx objects do not require // libc++abi just for this unreachable hook. Keep it weak so a toolchain's real // libc++abi definition wins if one is linked in anyway. extern "C" SIMDUTF_WEAK [[noreturn]] void __cxa_pure_virtual() noexcept { __builtin_trap(); } #endif ``` Finally, I wrote a script to audit the build with`\-fno\-exceptions`and`\-fno\-rtti`and check that no symbols such as`\_\_cxa\_guard\_\*`,`\_\_gxx\_personality`,`\_\_cxa\_throw`,`typeinfo`, or`\_\_dynamic\_cast`ever show up\. This was added to the simdutf CI to ensure that we never accidentally reintroduce libc\+\+abi dependencies in the future for`NO\_LIBCXX`builds\. ## Validation ### Internal simdutf is a correctness and performance critical library, so I had to be sure my changes didn't impact either of those\. I modified the pre\-existing test and benchmark suites to run in both`NO\_LIBCXX`and normal modes and ensured that all tests passed and benchmarks were unaffected\. The important part about this is that I committed the changes necessary to ensure that the`NO\_LIBCXX`mode is compatible with the existing test and benchmark suites\. This means that future changes to simdutf can continue to validate both\. ### External: Ghostty Next, I updated Ghostty to use the new simdutf from my fork, updated our builds to use`SIMDUTF\_NO\_LIBCXX`, and added our own suite of tests to verify our artifacts have no dependencies on libc\+\+ or libc\+\+abi\. Ghostty has a robust set of tests that verify our UTF\-8 decoding behavior \(especially invalid inputs\)\. And Ghostty has a built\-in benchmark suite that tests our UTF\-8 throughput in a variety of scenarios\. I ran all the Ghostty tests and benchmarks and verified that they all passed and that our UTF\-8 performance was unaffected, as expected\. ## Pull Request Getting something working and getting something merged are two different things\. I know too well as a maintainer myself the disparity between "this works" and "this can be merged\." I know the challenges of verifying someone's work and being confident in maintaining it going forward\. I know the challenge of opening a large PR and not having clarity of why it is being opened\. And I know the burden of recent AI slop\. So I put in the effort I would want from an all\-star contributor and tried to be that person for the simdutf maintainers\. First, I reviewed the entire diff \(yes, all ~3,000 lines of it\)\. Then I reviewed it again\. I re\-read the entire diff by hand three or four times\. I made multiple changes based on things I probably would've commented on myself, even if functionally it was fine\. Next, I hand\-wrote a detailed PR description that explains the motivation, the approach, the limitations, and the validation\. I wanted to make sure that the maintainers were aware of the details but also how much thought I put into the details\. Finally, I disclosed that I did use AI to assist me in writing the code\. But I made it clear that I manually reviewed everything, I didn't use AI in writing any of the PR description or comments, and that I was capable and comfortable as a human to defend and modify any proposed changes\. Ironically, the full diff took me about 2 hours to put together, but the extra validation work and PR preparation took me about 3 hours\. I spent more time on the human boundary than the code itself, as we should out of respect for the effort maintainers put into their projects\. ## Final Status The[simdutf PR](https://github.com/simdutf/simdutf/pull/959)is still under review\. Initial feedback is positive, and I'm open to any and all changes requested of me\. There's a possibility that the maintainers will not want to merge it, and that's okay, too\. If you want to use simdutf without libc\+\+ or libc\+\+abi, you can[use my fork](https://github.com/mitchellh/simdutf/tree/nolibcxx)in the meantime\. The same instructions for producing an amalgamated single\-file plus header build apply\. When building the C\+\+ and including the header, just make sure to define`SIMDUTF\_NO\_LIBCXX`to get the no\-libc\+\+ version of the library\. The[Ghostty PR](https://github.com/ghostty-org/ghostty/pull/12291)is now merged\. As such,`libghostty\-vt`no longer depends on libc\+\+ or libc\+\+abi for SIMD builds\. 1. libc\+\+ is the C\+\+ standard library \(e\.g\.`std::vector`,`std::string`, etc\.\) and libc\+\+abi is the C\+\+ ABI library \(e\.g\. exception handling, RTTI, etc\.\)\.[↩](https://mitchellh.com/writing/simdutf-no-libcxx#user-content-fnref-1) 2. Note that with SIMD disabled, libghostty\-vt has always had zero dependencies, not even libc\. It is a fully freestanding library\.[↩](https://mitchellh.com/writing/simdutf-no-libcxx#user-content-fnref-2)

Similar Articles

Libghostty Is Coming

Mitchell Hashimoto

Mitchell Hashimoto announces plans for libghostty, an embeddable library for terminal emulation, starting with libghostty-vt, a zero-dependency terminal sequence parser extracted from Ghostty.

Making cross-platform SIMD code pleasant

Lobsters Hottest

The author details the third iteration of the bx library's cross-platform SIMD abstraction, advocating for a typeless approach and SSA-style coding to simplify low-level performance optimization across different CPU architectures.

C++26 Shipped a SIMD Library Nobody Asked For

Lobsters Hottest

The article criticizes the new std::simd library in C++26, arguing it is slower than scalar loops, compiles slowly, and is outperformed by auto-vectorizers and alternative libraries like Google Highway, questioning its value after a decade-long standardization process.

Raylib v6.0

Hacker News Top

Raylib v6.0 releases as a lightweight, dependency-free C library for game development with support for multiple platforms and OpenGL versions.