Simdutf现在可以在没有libc++或libc++abi的情况下使用
摘要
Simdutf是一个快速的Unicode库,现在可以在不需要libc++或libc++abi的情况下使用,这使得它在嵌入式、WebAssembly和独立环境中更具可移植性。这一更改移除了Ghostty终端模拟器库中的最后一个libc++依赖。
暂无内容
查看缓存全文
缓存时间: 2026/05/16 03:37
# Simdutf 现在无需 libc++ 或 libc++abi 即可使用
来源:https://mitchellh.com/writing/simdutf-no-libcxx
随着这个 PR (https://github.com/simdutf/simdutf/pull/959#top) 的合并,[simdutf](https://github.com/simdutf/simdutf) 现在可以无需 libc++ 或 libc++abi (1) (https://mitchellh.com/writing/simdutf-no-libcxx#user-content-fn-1) 即可使用。Simdutf 曾是 `libghostty-vt` (2) (https://mitchellh.com/writing/simdutf-no-libcxx#user-content-fn-2) 中最后一个依赖 libc++ 的组件。在将 Ghostty 更新为使用这个新的 simdutf 构建 (https://github.com/ghostty-org/ghostty/pull/12291) 后,我们已成功从依赖中完全移除了 libc++ 和 libc++abi。
## 不依赖 libc++ 的好处
不依赖 libc++ 可使库更具可移植性(嵌入式、WebAssembly、独立环境)、简化交叉编译(无需特定目标的 C++ 标准库)、减小二进制体积,并能简化静态链接。作为一个通用低层库,simdutf 应尽量做到可移植和灵活。如果下游使用者可以轻松使用 libc++,那很好!但如果不能,他们也不应被阻止使用 simdutf,因为从根本上讲它并不需要 libc++。
## libc++ 与 libc++abi
要将程序从 libc++ 中剥离出来,涉及两部分。首先,`libc++` 是 C++ 标准库,提供 `std::vector`、`std::string` 等。如果你包含了 `<vector>`,甚至包含了作为 C++ 后备的 C 头文件如 `<cstdint>`,你就已经依赖了 libc++。更隐蔽的部分是 `libc++abi`,它提供 C++ ABI,包括异常处理、虚函数表、RTTI 等。如果你使用了任何需要这些功能的 C++ 特性,那么即使你完全没有引入任何 C++ 标准库头文件,你也在依赖 libc++abi。例如,下面这个最简 C++ 程序就依赖 libc++abi,因为它使用了函数局部静态变量,而后者需要线程安全的初始化,这是 C++ ABI 的一部分:
```cpp
struct Implementation {
int version;
};
const Implementation& get_impl() {
static const Implementation impl{1};
return impl;
}
int main() {
return get_impl().version;
}
```
## 将 simdutf 从 libc++ 中剥离
先来谈谈 `libc++`(而非 ABI)。Simdutf 是一个 C++ 库,大量使用了最新最强的 C++ 特性。虽然我个人不认识他,但 [Daniel Lemire](https://lemire.me/en/) 看起来非常喜欢充分利用 C++ 特性。因此,simdutf 是一个地道的 C++ 项目。为了让我的改动有被接受的机会,我必须确保项目能够继续使用 C++ 特性,而不带来麻烦。
### STL 使用情况
我采取的方法是引入一个 `stl_compat.h` 头文件 (https://github.com/mitchellh/simdutf/blob/nolibcxx/include/simdutf/internal/stl_compat.h),将所有 C++ 标准库类型集中管理。在正常的 libc++ 模式下,`stl_compat.h` 中的所有内容都是简单的 include 或对应 C++ 标准库类型的最小别名,没有运行时开销。在 `NO_LIBCXX` 模式下,`stl_compat.h` 提供它自己实现的、simdutf 所使用的 C++ 类型,但只实现到足以兼容 simdutf 需要的程度。例如,`stl_compat.h` 提供了自己的 `std::pair` 实现。因此,整个 diff 中的修改大体上都是这样:
```cpp
-std::pair
+internal::pair
arm_convert_latin1_to_utf32(const char *buf, size_t len, char32_t *utf32_output) {
```
### ABI 兼容性
我的目标是尽可能保留 ABI 兼容性。一些 [公共 ABI](https://en.wikipedia.org/wiki/Application_binary_interface) 暴露了 C++ 类型,例如 `std::string`,因此在这些场景下,ABI 必须被破坏。除此以外,ABI 完全保留。鉴于 `SIMDUTF_NO_LIBCXX` 是一个新特性,它会在编译单元上应用改动,我认为**仅当使用此标志时**破坏 ABI 是可以接受的。在未使用 `NO_LIBCXX` 标志的现有情况下,ABI 完全保留,用户可以更新 simdutf 而无需破坏 ABI。
令我惊喜的是,ABI 破坏非常微小,仅涉及少数诊断函数(例如获取当前实现的名称)以及处理其他 C++ 类型的辅助函数(例如文本编码的 `std::string`)。由于使用 `SIMDUTF_NO_LIBCXX` 的人本身对 libc++ 不感兴趣,这些 ABI 破坏反而更像是一个特性而非缺陷。
## 将 simdutf 从 libc++abi 中剥离
这一任务要复杂得多。主要问题在于,`libc++abi` 的依赖通常不会以明显的源码级 `#include` 形式出现。它们会因为编译器在普通语言特性背后静默地发出对 C++ ABI 运行时的调用而显现。要检测它们,我不得不编写一个脚本来反汇编目标文件,并查找诸如 `__cxa_guard_acquire` 之类的符号。
在 simdutf 中,最大的元凶是运行时分发层。原始代码大量依赖函数局部静态变量。在 C++ 中,这些局部变量由线程安全的初始化辅助函数(如 `__cxa_guard_acquire` 和 `__cxa_guard_release`)保护,而这些函数由 C++ ABI 运行时提供。因此,即使代码中没有提到 libc++abi,编译出的目标文件仍然依赖它。
解决方法是,在 `NO_LIBCXX` 模式下使用翻译单元级静态变量替代函数局部静态变量:
```cpp
#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
```
接下来,simdutf 将每个后端建模为抽象 `implementation` 接口的子类。这种设计可以保留,但抽象类的虚函数表仍然会引用 `__cxa_pure_virtual`,用于那些不可调用的纯虚函数入口。在 `SIMDUTF_NO_LIBCXX` 模式下,我选择提供一个极小的本地 shim,并确保运行时永远不会实际到达它。我将它标记为弱符号,以便在存在 C++ ABI 的情况下可以被覆盖。
```cpp
#ifdef SIMDUTF_NO_LIBCXX
// 抽象实现的虚函数表仍然包含纯虚函数槽位,尽管在此构建模式下正确的分发永远不会到达它们。
// 提供尽可能窄的 ABI shim,使得更严格的 no-libcxx 对象不会仅仅因为这个不可到达的钩子而需要 libc++abi。
// 保持弱符号,以便如果工具链中链接了真正的 libc++abi 定义,它优先使用。
extern "C" SIMDUTF_WEAK [[noreturn]] void __cxa_pure_virtual() noexcept {
__builtin_trap();
}
#endif
```
最后,我编写了一个脚本来审计使用 `-fno-exceptions` 和 `-fno-rtti` 的构建,并检查是否出现了诸如 `__cxa_guard_*`、`__gxx_personality`、`__cxa_throw`、`typeinfo` 或 `__dynamic_cast` 等符号。这一检查已被加入到 simdutf 的 CI 中,以确保我们未来不会在 `NO_LIBCXX` 构建中意外重新引入 libc++abi 依赖。
## 验证
### 内部验证
Simdutf 是一个注重正确性和性能的库,因此我必须确保我的改动不会影响这两方面。我修改了现有的测试和基准套件,使其能在 `NO_LIBCXX` 和正常两种模式下运行,并确保所有测试通过、基准性能不受影响。关键在于,我提交了必要的改动来确保 `NO_LIBCXX` 模式与现有的测试和基准套件兼容。这意味着未来对 simdutf 的修改可以继续对两者进行验证。
### 外部验证:Ghostty
接着,我将 Ghostty 更新为使用我分支中的新 simdutf,更新了我们的构建以使用 `SIMDUTF_NO_LIBCXX`,并添加了我们自己的测试套件来验证我们的产物不依赖 libc++ 或 libc++abi。Ghostty 拥有一套稳健的测试,用以验证 UTF-8 解码行为(尤其是无效输入)。Ghostty 还有一个内置的基准套件,测试各种场景下的 UTF-8 吞吐量。我运行了 Ghostty 的所有测试和基准,验证它们全部通过,并且我们的 UTF-8 性能未受影响,这与预期一致。
## 拉取请求
让代码工作起来和让它被合并是两回事。作为维护者,我深知“这能工作”和“这可以合并”之间的差距。我理解验证他人工作、并未来有信心维护它所带来的挑战。我理解提交一个大型 PR 却未明确说明动机的困难。我也理解近年来 AI 生成的低质量贡献带来的负担。因此,我投入了希望从一位杰出贡献者身上看到的努力,并努力成为 simdutf 维护者眼中的这样一个人。
首先,我通读了整个 diff(是的,整整约 3000 行)。然后我又读了一遍。我手动将整个 diff 从头到尾读了三到四遍。我根据自己的判断做了多处修改——即便从功能上看那些代码已经没问题,但我自己可能会对这些问题提出意见。接着,我亲手撰写了一份详细的 PR 描述,解释了动机、方法、局限性和验证结果。我希望确保维护者不仅了解细节,也了解我对这些细节投入了多少思考。
最后,我坦承我使用了 AI 来协助编写代码。但我明确表示,我手动审查了所有内容,没有使用 AI 撰写任何 PR 描述或评论,并且我有能力也有信心以人的身份来捍卫或修改任何拟议的修改。
讽刺的是,整个 diff 我只用了大约 2 小时来编写,但额外的验证工作和 PR 准备却花了我大约 3 小时。我在“人工边界”上花费的时间比在代码本身还多,这是对维护者投入项目精力的应有尊重。
## 最终状态
[Simdutf 的 PR](https://github.com/simdutf/simdutf/pull/959) 仍在评审中。最初的反馈是积极的,我愿意接受任何要求的修改。也有可能维护者不愿意合并它,那也没关系。如果你希望在不依赖 libc++ 或 libc++abi 的情况下使用 simdutf,可以暂时使用[我的分支](https://github.com/mitchellh/simdutf/tree/nolibcxx)。生成合并单文件加头文件构建的说明同样适用。在构建 C++ 代码并包含头文件时,只需定义 `SIMDUTF_NO_LIBCXX` 即可获得无 libc++ 版本的库。
[Ghostty 的 PR](https://github.com/ghostty-org/ghostty/pull/12291) 现已合并。因此,`libghostty-vt` 在 SIMD 构建中不再依赖 libc++ 或 libc++abi。
1. libc++ 是 C++ 标准库(例如 `std::vector`、`std::string` 等),libc++abi 是 C++ ABI 库(例如异常处理、RTTI 等)。↩ (https://mitchellh.com/writing/simdutf-no-libcxx#user-content-fnref-1)
2. 请注意,在启用 SIMD 的情况下,libghostty-vt 一直具有零依赖,甚至不依赖 libc。它是一个完全独立的库。↩ (https://mitchellh.com/writing/simdutf-no-libcxx#user-content-fnref-2)
相似文章
fearless_simd v0.7:64位整数、改进的泛型、SSE2 及即将到来的 v1.0
fearless_simd v0.7 新增了 64 位整数支持、SSE2 级别、改进的泛型及更多操作,v1.0 即将发布。
在dsymutil中采用并行DWARF链接器
苹果的dsymutil工具用于将DWARF调试信息链接到自包含的捆绑包中,现在正在采用并行DWARF链接器来解决类型去重中的单线程瓶颈,尽管由于输出并非二进制完全相同而在验证方面面临挑战。
Libghostty 即将到来
Mitchell Hashimoto 宣布了 libghostty 的计划,这是一个可嵌入的终端模拟库,首先推出 libghostty-vt,这是一个从 Ghostty 中提取的零依赖终端序列解析器。
使用DMD自举GDC
作者描述了如何在FreeBSD上使用DMD编译器和一个小的包装程序自举GNU D编译器(GDC),挑战了GCC关于GDC必须使用GDC构建的说法。
让编写跨平台 SIMD 代码变得愉快
作者详细介绍了 bx 库跨平台 SIMD 抽象的第三次迭代,倡导无类型方法和 SSA 风格编码,以简化不同 CPU 架构上的底层性能优化。