TL;DR:xclang 是我们为 clice 开发的 Clang 交叉编译工具链,其实就是 Clang 版的 rustup,把编译器、链接器,以及 Linux、Windows、macOS 的 x64 和 arm64 一共六个目标的 sysroot 和预编译好的运行时打包在一起,整个包不到 100 MB,加一个 --target 就能交叉编译。编译出的程序会把 C++ 运行时静态链接进去,拷一个文件过去,就能在 glibc 2.17 以上的 Linux、Windows 10 以上和 macOS 13 以上的系统上运行。Apple 和 Microsoft 的 SDK 没有打包进去,用户可以用 xclang sdk fetch 从官方下载,这样在 Linux 上也能编译 macOS 和 MSVC 目标。它可以直接在 CMake 和 Bazel 中使用,import std 开箱即用。编译器本身使用 PGO + ThinLTO 构建,C++ 的编译速度和 LLVM 官方的发布版本基本持平,在 Windows 上还要更快。

在 打造优雅的 C++ 跨平台开发与构建 Workflow 中,我们用 pixi 统一了 clice 在三个平台上的开发环境,在 Linux 上已经可以做到不依赖系统里的任何工具链了。不过那篇文章最后还剩了一个问题没解决:Windows 和 macOS 的 SDK 因为许可证的原因不能分发,开发者还是得自己安装 MSVC 和 Windows SDK,或者 Xcode Command Line Tools,当时我的结论是「这个问题暂时没有完美的替代方案」。最近我们做了 xclang,这个问题总算有了一个比较完整的解决方案。

Background

其实那套方案除了 SDK 之外还有一个问题。clice 在三个平台上用的是三个不同的 C++ 标准库,Windows 上是 MSVC STL,Linux 上是 libstdc++,macOS 上是 libc++。而 clice 打算迁移到 C++20 模块(毕竟我们自己要支持 module),想要在三个平台上都稳定地使用 import std,用三套不同的标准库是做不到的。目前 Clang 的模块支持是最好的,libc++ 的 std 模块也是最完整的,所以最好的办法就是在三个平台上都使用 Clang + libc++。

那为什么不在 Linux 上用 Clang 配合系统的 libstdc++ 呢?libstdc++ 同样提供了 std 模块,看起来是很自然的选择。说实话,我们之前在这条路上踩了大量的坑,主要原因是编译器在模块实现上的 BUG。测试很难覆盖到所有情况,每个编译器和自己的标准库搭配时才测试得最充分,而标准库里又有大量复杂的模板,所以我不建议用 Clang 搭配 libstdc++ 的 std 模块。具体的细节就不在这篇文章里展开了,后面我们会单独写一篇关于模块的文章。

于是我们需要这样一套工具链:三个平台上都是 Clang + libc++,Linux 上要用低版本的 glibc,Windows 和 macOS 的 SDK 问题也要有办法解决。这就是 xclang,一套用于交叉编译的 Clang 工具链,开箱即用,把整套 toolchain 打包在一起,包括编译器、链接器、各种二进制工具,以及六个目标的 sysroot 和运行时。这六个目标是 Linux x64 和 arm64 (glibc 2.17),Windows x64 和 arm64 (MinGW-w64 + UCRT),macOS arm64 和 x64,每个目标的 libc++、libc++abi、libunwind 和 compiler-rt 都是预编译好的,Linux 和 MinGW 目标的 C 库头文件和库也都在里面,macOS 的 C 库则来自 Apple 的 SDK。交叉编译只需要加一个 --target,比如

clang++ --target=aarch64-w64-mingw32 hello.cpp -o hello.exe

从 23.1.2.7 开始还支持了 MSVC 目标,也就是使用 Microsoft 的 CRT 和 STL 的 Windows x64 和 arm64,在任何 host 上都可以构建。编译器是原版的 LLVM,只加了几个还在往上游提交的补丁,Linux、Windows、macOS 的 x64 和 arm64 一共六个 host 都有对应的包。每个包只有 86 到 94 MB,相比之下 LLVM 官方的发布包有 0.9 到 2 GB,而且只带了 host 自己的运行时,也没有其他目标的 sysroot。

以前想要交叉编译,往往是一个目标一套工具链,比如发行版的包、MinGW GCC 或者 Docker 镜像,每一套的编译器版本都不一样。而 xclang 的所有目标都用同一个编译器,编译器修了一个 BUG 或者加了一个警告,所有目标都会同时用上。

xclang 是为 clice 开发的,最先用上它的就是 clice 的 release 构建,catter 也在用它构建,xclang 自己的 CI 还会在 Linux 上为 Windows 构建 kotatsu 的测试,再拿到 Windows 上运行。

import std

很多人知道 C++20 有了模块,但是它实际用起来是什么样子,现在到底能不能用,其实并不清楚。好消息是现在就可以试一试,新建一个目录,放入下面的 CMakeLists.txt

cmake_minimum_required(VERSION 3.28)

include(FetchContent)
FetchContent_Declare(xclang
    GIT_REPOSITORY https://github.com/clice-io/xclang
    GIT_TAG latest)
FetchContent_MakeAvailable(xclang)
include(${xclang_SOURCE_DIR}/packages/cmake/xclang.cmake)

project(hello LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_EXTENSIONS OFF)

find_package(xclang REQUIRED CONFIG)

add_executable(hello main.cpp)
target_link_libraries(hello PRIVATE xclang::std)

和 main.cpp

import std;

int main() {
    std::vector<std::string> targets{"windows", "linux", "macos"};
    std::ranges::sort(targets);
    try {
        throw std::runtime_error(std::format("{} targets, the first {}", targets.size(), targets[0]));
    } catch (const std::exception& e) {
        std::println("hello from xclang: {}", e.what());
    }
}

然后执行

cmake -G Ninja -B build
cmake --build build

configure 的时候 FetchContent 会把当前 host 的工具链下载到用户的缓存目录里(GIT_TAG latest 是一个指向最新 release 的分支),所以除了 CMake 3.28 和 Ninja 1.11 以外,机器上不需要提前装任何东西。想要编译到其他目标的话,可以用 xclang 目录下的 toolchain 文件 lib/cmake/xclang/toolchain.cmake,再加上 -DXCLANG_TARGET=x86_64-w64-mingw32,xclang::std 也会跟着为这个目标构建。除了 CMake,Bazel 也可以直接使用,在 deps 里加上 @xclang//bazel:std 就行了,需要 Bazel 9 并开启 --experimental_cpp_modules。另外也可以通过 pixi/conda、release 里的压缩包或者 Bazel registry 安装,拿到的都是同一套工具链,具体可以参考 文档。

能跑起来一个 import std 的 demo 其实不难,CMake 的文档、LLVM 和 GCC 都有现成的例子,真正的问题是在生产环境里用起来。在 语言服务器之外 里我引用过一句话,「模块在编译器前端是已解决的问题,在其他所有方面都是未解决的问题」,模块需要编译器、构建系统和编译缓存一起配合才能用。clice 是一个不小的项目,现在已经迁移到了模块,迁移的过程中我们遇到了很多 corner case。

首先,std 模块必须和 import 它的文件用一致的编译选项,所以没法直接发布一个预编译好的 std 模块。模块文件构建时的语言选项,比如 -std、GNU 扩展、-fno-exceptions、-fno-rtti,只要和 import 它的文件不一样,Clang 就会直接拒绝使用,宏、头文件搜索路径和优化等级倒是可以不一样。如果一个 CMake target 要求的标准比构建 std 时用的更新,就会报 C++26 was disabled in precompiled file 这样的错误。所以 xclang 会在每次构建的时候,用这次构建的选项去编译 std 和 std.compat,作为一个普通的库给你链接,也就是上面的 xclang::std。如果某个 CMake target 的选项和其他的不一样,可以用 xclang_add_std(<name>) 单独给它生成一个。

xclang::std 也不依赖 CMake 的实验性开关。CMake 自己的 import std 支持在 4.4 中仍然要通过 CMAKE_EXPERIMENTAL_CXX_IMPORT_STD 开启,而且这个开关的值会随着 CMake 的版本变化。而 xclang::std 其实就是一个普通的库,只不过源文件是模块接口,所以 CMake 3.28 的模块支持就够了。

然后是编译缓存。ccache 目前还不支持模块,算 key 的时候不会把模块文件的内容算进去,所以没法用在模块化的项目上。Bazel 则是支持模块的,它按每个 action 所有输入的内容来算 key,模块文件也算在里面。xclang 编译模块的时候还会加上 -fmodule-file-home-is-cwd,让模块文件里的路径都相对于 execution root,这样所有机器都能共用一份缓存。clice 的 CI 就是用 Bazel 构建的。

注意,header unit (import <vector>;) 目前 CMake 和 Bazel 都不会构建

Design

Config files

每个目标的 sysroot 和运行时在哪里,是通过配置文件告诉 Clang 的。Clang 在编译某个目标的时候会读取对应的配置文件,比如 --target=aarch64-w64-mingw32 会读取 clang++ 旁边的 aarch64-w64-windows-gnu.cfg。xclang 在 bin/ 下为每个目标都写了一个,比如 Linux 目标的配置文件里写的就是这个目标的 --sysroot、-rtlib=compiler-rt -unwindlib=libunwind -stdlib=libc++、-static-libstdc++ -static-libgcc 和 -fuse-ld=lld。里面的路径都是相对路径,所以工具链解压到哪个目录都能用。配置文件对本机编译同样生效,在 Linux 上直接 clang++ main.cpp,用的也是 glibc 2.17,和机器上的 glibc 无关。

最早的 23.1.2.1 其实是把这些默认值直接编译进 Clang 的(CLANG_DEFAULT_CXX_STDLIB 这类选项),但是这些默认值是写在 driver 里的,而 libclang 里也有一份 driver。clice 为了找到头文件的搜索路径,会用 Clang 的 driver 来代替编译数据库里的 g++ 命令,默认值编译进去之后,driver 给这个 g++ 命令算出来的就变成了 libc++ 的头文件,而不是 libstdc++ 的。所以从 23.1.2.2 开始,默认值都放到了配置文件里,libclang 里的 driver 和上游 Clang 的行为一致,想要一个原版的 Clang 加上 --no-default-config 就行了。

sysroot 本身也只是普通的目录,任何同一个大版本的 Clang 指定 --sysroot 和 -resource-dir 之后都可以用它们来交叉编译。配置文件也都是纯文本,每个目标具体用了哪些选项,打开看一眼就知道了。

Hermeticity

这里说的密封性 (hermeticity),指的是程序运行时只依赖系统自带、而且没法随程序分发的那些库,比如 Linux 上的 glibc、macOS 上的 libSystem、Windows 上的系统 DLL 和 UCRT,其他的全部静态链接进程序里,构建的时候除了工具链本身,唯一的外部输入就是厂商的 SDK。所以编译出来的程序就是一个文件,拷到 glibc 2.17 以上的 Linux、Windows 10 以上或者 macOS 13 以上的机器上就能跑。代价是程序体积会大一些,而且每个动态库都有一份自己的 libc++,在 Linux 和 macOS 上,一个动态库里抛出的标准库异常,到另一个动态库里只能用 catch (...) 捕获,所以动态库之间的接口最好是 C 接口。

Vendor SDKs

现在回到那篇 workflow 文章最后的问题。Apple 的 SDK 协议和 Visual Studio 的许可证都只允许使用,不允许再分发,所以 xclang 不分发 SDK,而是由用户自己从厂商那里下载。从 23.1.2.7 开始,每个包里都带了一个 Rust 写的 xclang 命令,xclang sdk fetch 会在用户接受许可证 (--accept-license) 之后下载 SDK,版本和 sha256 都是固定的,这样 xclang 自己的包仍然可以自由分发。编译 MSVC 目标,或者在 Linux 和 Windows 上编译 macOS 目标的时候,需要这样下载,在 macOS 上则直接使用 Xcode 的 SDK。

下载 SDK 并不需要安装 Xcode 或者 Visual Studio。Apple 的 SDK 其实就在 Command Line Tools 的安装包里,这个包可以直接从 Apple 的服务器上下载,不需要 Apple ID。安装包是 xar 格式,里面是 pbzx(由一块块的 xz 组成),再里面是 cpio,这三层 xclang 都是用自己的代码读的,一共几百行,所以在任何 host 上都能下载并解开。不过 iOS 等其他设备的 SDK 只有完整的 Xcode 里才有,而且需要 Apple ID,这是绕不过去的限制。

Microsoft 这边,CRT 和 STL 来自 Visual Studio 自己的 .vsix 包,Windows SDK 来自 NuGet 上的 Microsoft.Windows.SDK.CPP,两者都是 zip,在任何 host 上都能解压。做同样事情的工具里比较有名的是 xwin,它走的是解压 MSI/CAB 的路线,而且没有 macOS 和 Windows arm64 的二进制,而 xclang 正好需要在这两个平台上运行。

MSVC 目标用的是 Microsoft 所说的 hybrid CRT:VC runtime 和 STL 静态链接(相当于 /MT),UCRT 则使用 Windows 10 以后系统自带的 ucrtbase.dll。这样程序只会加载 Windows 自己的 DLL,并且和它加载的所有 DLL 共用同一个堆、同一个 errno 和同一套 stdio。

PGO

编译器本身快不快,很大程度上决定了构建要多久。在 23.1.2.1 中,如果构建编译器时不使用 profile,用它编译 fmt 的耗时是 LLVM 官方版本的 1.15 到 1.37 倍,而加上 profile 能减少 16% 到 34% 的时间。xclang 的 Clang 和 lld 在所有 host 上都使用 PGO + ThinLTO 构建,用的是同一份在 Linux 上采集的 profile。

训练的时候,让插桩版的 Clang、clang-scan-deps 和 lld 跑它们平时实际会跑的东西:用 -O0 -g 和 -O2 编译 sqlite、abseil 这些 C 和 C++ 源码,编辑器会用到的预编译头和 preamble,C++20 模块(包括 libc++ 的 std 和 std.compat),CMake 运行的 P1689 依赖扫描,代码补全请求,以及通过 lld 进行 ELF、ThinLTO 和 COFF 链接。整个训练大约调用了 1700 次编译器,跑了 23 分钟,每个 release 一份 profile,和 release 一起发布。

为什么一份 Linux 上的 profile 就够了呢?这要从两种插桩方式说起。IR 插桩 (-fprofile-generate) 是在经过早期优化 pass 之后的 LLVM IR 上计数的,用那时的控制流图的 hash 来识别函数,而这些 pass 会用到目标平台的 cost model,所以同一个函数在 x86-64 和 arm64 上的控制流图并不一样,在一个平台上采集的 profile 到另一个平台上就对不上了。前端插桩(-fprofile-instr-generate,也就是 LLVM_BUILD_INSTRUMENTED=Frontend)则是在 Clang 的 AST 上计数,根据源码来算函数的 hash,在所有平台上都是一样的。

xclang 用的是前端插桩。在 23.1.2.1 之前的实验中,Linux x64 上采集的 profile 在六个 host 上都只有 0 或 1 个函数对不上(一共 113,000 个函数),和把六个 host 各自的 IR profile 合并起来用相比,速度的差距也在噪声范围内,比如在 Linux x64 上编译 abseil,相对于不使用 PGO 的耗时比,前者是 0.725,后者是 0.722。这样训练只需要在一台 Linux runner 上跑一次,不需要在每个 host 上分别构建插桩版本再训练,也不需要模拟器。

不过这里还有一个问题,profile 是根据 mangle 之后的函数名来查找的,而 size_t 和 uint64_t 在 Linux 上是 unsigned long (m),在 Windows 上是 unsigned long long (y),macOS 上的 uint64_t 也是 unsigned long long,int64_t 也有 l 和 x 的区别。于是一个参数是 size_t 的函数在 Windows 上就换了一个名字,拿不到任何计数。xclang 用 -fprofile-remapping-file 指定了一个 toolchain/pgo/remap.txt,告诉编译器这些 mangling 是同一个东西。加上之后,编译 abseil 和 LLVM 的 Sema 的耗时比,在 macOS arm64 上从 0.896 / 0.923 降到了 0.794 / 0.845,在 Windows x64 上从 0.787 / 0.824 变成了 0.772 / 0.829。

测速度用的是训练中没出现过的代码:fmt 的十个测试、lua 的 C 源码,以及预编译 libc++ 的 std 模块,分别用 -fsyntax-only、-O0 -g 和 -O2 编译,每次编译一个文件。几个编译器在同一台机器上轮流运行,取多轮的中位数,每个 host 再取五台 runner 的中位数,对照的是 LLVM 官方同版本的 release 构建。下面是 23.1.2.6 相对 LLVM 23.1.2 的耗时比,小于 1 表示更快

host fmt (syntax / O0 / O2) lua (syntax / O0 / O2) std (O0 / O2)
Linux x64 1.035 / 1.009 / 1.069 1.118 / 1.062 / 1.109 1.013 / 1.022
Linux arm64 1.000 / 0.976 / 1.042 1.082 / 1.011 / 1.075 0.970 / 0.994
macOS arm64 0.902 / 0.935 / 1.014 1.169 / 0.971 / 0.959 0.980 / 0.890
Windows x64 0.798 / 0.825 / 0.890 1.083 / 1.050 / 1.001 0.777 / 0.849
Windows arm64 0.888 / 0.906 / 0.954 1.271 / 1.212 / 1.167 0.925 / 0.940

注意 runner 的噪声很大,几个百分点的差距其实说明不了什么。Linux x64 上 LLVM 官方的版本额外做了 BOLT 优化,所以 xclang 在这里稍慢一些;而 Windows 上官方的 Clang 是用 MSVC 构建的,只有 PGO 没有 LTO。Windows 上的 clang++.exe 只是一个启动器,它会再去启动真正干活的 llvm.exe,多了一次进程启动,在比较小的 C 文件上能看出来。如果和 Apple 自己的 Clang 比,在两种架构的 macOS 上,Apple Clang 编译 fmt 要比 xclang 多花 17% 到 29% 的时间,编译 lua 的 C 代码则从少花 25% 到多花 5% 不等。

Comparison

和 xclang 思路最接近的是 zig cc,下面是和它以及 LLVM 官方发布版本的对比

xclang zig cc LLVM 官方发布
目标 Linux、Windows(MinGW、MSVC)、macOS 的 x64 和 arm64 几十个 仅 host
sysroot 和 SDK 自带 sysroot,SDK 由用户下载 带 libc 源码和 stub,首次使用时构建 无
程序里的 C++ 运行时 静态链接 libc++(MSVC 目标是 Microsoft 的 STL) 静态链接 libc++ 仅 host 的运行时
Linux 程序要求的 glibc 2.17 及以上 每个目标可选,默认 2.31 构建机的版本(Ubuntu 22.04)
编译器的 PGO 所有 host 都是 PGO + ThinLTO 未说明 Linux/macOS 是 PGO + ThinLTO,Windows 只有 PGO
下载大小 86~94 MB 55 MB 0.9~2 GB

zig cc 也是下载下来就能用,加一个 -target 就能交叉编译。区别在于 zig 带的是 libc++、libc++abi、libunwind、compiler-rt 和几个 C 库的源码,第一次用到某个目标的时候现场构建,然后缓存起来,所以下载包可以做得很小。xclang 则是直接带上六个目标预编译好的运行时,第一次用的时候不用等它构建,每个人拿到的运行时也都是逐字节相同的。当初在那篇 workflow 文章里放弃 zig cc,其中两个原因就和它的打包方式有关:它把所有 glibc 版本的头文件放在一起用宏来区分,导致 __has_include 误判;它现场构建 libc++ 的时候不带模块,我没法让它用我自己构建的 libc++ 模块,import std 也就用不了。

不过 zig cc 目前支持的目标确实更多,比如 musl、各种 BSD 和 WASI,每个目标还可以任选 glibc 版本(比如 x86_64-linux-gnu.2.17)。它在任何 host 上都可以编译 macOS 目标,用的是 Apple 的 libc 头文件和一个 libSystem 的 stub,而 xclang 用的是用户自己下载的 Apple 官方 SDK。另外 zig 0.17.0 用的是 LLVM 22,并且为了绕开一个 regression 关闭了循环向量化,而 xclang 跟着 LLVM 的 release 走,除了前面说的几个补丁,用的就是原版的 Clang;zig 也不带 ASan 的运行时,它的 windows-msvc 目标需要本机装好 Visual Studio。

其他的方案大多只覆盖了一部分场景。cross-rs 是在 Docker 镜像里用 GCC 交叉工具链跑 cargo (glibc 2.31,:centos 镜像是 2.17),Apple 的目标则「due to licensing reasons」没有提供镜像,和我们当初遇到的是同一个问题。llvm-mingw 只支持 Windows,除非加 -static,否则链接的是 libc++ 的 DLL。conda-forge 的编译器(也就是那篇 workflow 文章里 pixi 用的)会通过 run_exports 把 conda 包里的 C++ 运行时加进程序的运行时依赖,在 conda 环境里没有问题,程序一旦拿到 conda 环境外面,就没人去装这些依赖了。Android NDK 和 wasi-sdk 倒是和 xclang 一样,都是把 Clang 和 sysroot 打包在一起发布的,只不过 xclang 面向的是桌面平台。更完整的对比可以看 xclang 的文档。

How it was built

xclang 的代码全部是 agent 写的,模型全程用的是 Opus 5.5。第一个 commit 是在 2026 年 9 月 21 日,截至 10 月 7 日一共有 177 个 commit,发布了 23.1.2.1 到 23.1.2.8 一共 8 个版本。整个过程一共开了 1 个 lead session 和 23 个 worker session,用了 294 个 subagent,调用了 9,403 次工具,其中 7,623 次是 shell 命令。写出来的代码大约 1.9 万行,包括脚本、测试、CMake 和 Bazel 的配置、Rust 写的命令行工具和 CI workflow,另外还有大约 4.2 万词的文档。

这种工作其实特别适合交给 agent。构建一次 LLVM 要两个小时左右,一个问题往往要反复试很多次才能定位,如果是我自己来做,大部分时间都会花在等 CI 上,而且我也不可能一直盯着,而 agent 可以 24 小时一直试下去。实际上将近一半的工具调用都是在北京时间 0 点到 6 点之间。在这类工作上,agent 的效率比人高太多了,比如有一次大改文档,一共 37 页,每一条命令都在六台机器上实际跑过,一个晚上就完成了。

比较重的构建都是放到 CI 上跑的,本地只做一些轻量的工作,agent 需要在 Windows、macOS 或者 Linux 上构建和测试的时候,会通过 box 租用真实的 GitHub Actions runner。交叉编译出来的程序也都会拿到对应平台的真机上运行,不用模拟器,这些都很依赖 CI 的并发。GitHub 免费版最多同时跑 20 个 job,其中 macOS 只有 5 个,为此我们专门买了 GitHub Enterprise,并发提高到了 500 个,其中 macOS 50 个,实际用下来效率提高了很多。目前 CI 一共跑了 278 次 workflow、4,988 个 job,大约 499 个 runner 小时,其中完整的 release 流水线跑了 75 次。

过程中 agent 做了大量的实验,比如

  • ld64.lld 的异常问题:第一个版本的测试里,一个用 ThinLTO 编译链接的程序在 arm64 macOS 上没能捕获自己抛出的异常,直接 terminate 了,换成 LLVM 官方的 ld64.lld 也一样,于是先临时把 macOS 目标的链接器换成了 Apple 的 ld。最后定位到是 LTO 后端生成的一个空目标文件里唯一的符号 ltmp0 和 main 地址相同,ld64.lld 让它在 __unwind_info 里占了 main 的位置,带上上游的修复之后,从 23.1.2.5 开始 macOS 又用回了 ld64.lld
  • ThinLTO 缓存:对于基于 libclang 的工具,ThinLTO 的链接缓存效果非常明显,clice 在 Linux x64 上重新链接一次要 353 秒,有完整缓存的话只要 6 秒左右。agent 还发现 GitHub Actions 恢复缓存的时候会把所有文件的访问时间设成恢复的时间,链接器的清理机制就不会触发,缓存只会越来越大,clice 的缓存有 350 到 650 MB
  • 可复现构建:每个 host 的包都会在两台机器上各打一次,要求逐字节相同(tar 排序、使用 commit 时间、去掉 owner、xz 按固定大小分块)
  • 包的体积:Clang、lld 和大部分二进制工具合成了一个 llvm 程序,每个工具名只是指向它的链接,加上其他几个改动,Windows x64 的包从 540 MB 的 zip 降到了 82 MB,Linux x64 从 148 MB 降到了 83 MB(都是当时的大小)。共享的 libLLVM 也考虑过,但是因为速度的原因没有采用

上游的 bug 也碰到了不少,目前 xclang 在 LLVM 上打了 8 个补丁,主要是这些:

  • Clang:模板偏序(在类模板里触发代码补全会让 Clang 崩溃),代码补全(两个),用 MinGW 构建的 Clang 通过 Setup API 查找 Visual Studio,clang -E 在 CRLF 下原始字符串字面量的行号
  • libc++:std::format_to 往容器里写的时候,参数长度正好是 256 的倍数会写越 256 个 code unit 的栈上缓冲区
  • lld:上面提到的 Mach-O unwind 问题
  • TextAPI:读取 macOS 27 SDK 中的 arm64e.x1

等 LLVM 的 release 包含了对应的修复,这些补丁就会去掉。

Roadmap

目前 xclang 自带的是六个最常用的目标,后面其他的目标会各自单独打包,构建需要的时候再通过 xclang target add 下载。对于预编译的运行时没有覆盖到的选项,比如 MemorySanitizer 和 libc++ hardening,后面会支持在需要的时候从源码构建运行时。新的目标里,musl 的 Linux x64 和 arm64(可以编译出完全静态的程序)已经在计划中,版本更高的 glibc、更多的 Linux 架构、WebAssembly、Android、BSD 和裸机还在考虑,iOS 系列则还在调研。

MSVC 目标后面会支持使用 libc++ 作为 C++ 标准库。xclang 的 Bazel module 目前还不能构建 MSVC 目标,也不能在 Linux 和 Windows 上构建 macOS 目标,这些现在只能用 CMake,后面也会补上。PGO 的训练集也会继续扩充,加上 Objective-C、clang-cl、Mach-O 链接和 clang-tidy 的检查,BOLT 还在调研。LLVM 的版本也会继续跟进,包括带有 arm64e.x1 修复的 23.1.3,以及之后的 24.x。

最后说一下 clice。clice 现在已经迁移到了 C++ 模块,这也是 agent 时代的 clice 里定下的方向,迁移过程中踩过的坑,后面会在那篇模块的文章里详细说。

感谢阅读!感兴趣的话欢迎加入我们的 QQ 交流群:https://qm.qq.com/q/tSD3D81fpu,xclang 的问题也欢迎直接在 GitHub 上提 issue。